@absolutejs/absolute 0.20.0-beta.169 → 0.20.0-beta.170
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/CHANGELOG.md +80 -0
- package/README.md +232 -0
- package/changelog.json +228 -0
- package/dist/angular/browser.js +7 -8
- package/dist/angular/browser.js.map +6 -5
- package/dist/angular/components/core/streamingSlotRegistrar.js +6 -2
- package/dist/angular/components/core/streamingSlotRegistry.js +7 -3
- package/dist/angular/index.js +555 -156
- package/dist/angular/index.js.map +29 -23
- package/dist/angular/server.js +555 -156
- package/dist/angular/server.js.map +29 -23
- package/dist/build/buildWorker.js +14 -8
- package/dist/build.js +4949 -1941
- package/dist/build.js.map +72 -42
- package/dist/cli/{add-7azr0c83.js → add-6a7pjkn9.js} +11 -7
- package/dist/cli/{analyze-3cyek6v3.js → analyze-606fshwa.js} +1 -1
- package/dist/cli/{build-25rz8v25.js → build-g81xqbb9.js} +1 -1
- package/dist/cli/{compile-4jnfxts9.js → compile-9y1w9zpj.js} +3 -3
- package/dist/cli/config/server.js +10699 -1538
- package/dist/cli/{dev-9tk5hn2e.js → dev-hyeb89hp.js} +5 -1
- package/dist/cli/{doctor-4nv2c2j3.js → doctor-h55y9hj1.js} +1 -1
- package/dist/cli/{generate-v75p2fct.js → generate-20x1pkf7.js} +4 -4
- package/dist/cli/{htmx-427swtdz.js → htmx-6mz2h7k1.js} +2 -2
- package/dist/cli/{index-bhbs6d5z.js → index-32ge2w1f.js} +12 -4
- package/dist/cli/{index-a4ws2jwd.js → index-3wz3mktm.js} +1 -1
- package/dist/cli/{index-b7hjgknf.js → index-9sksd8c8.js} +13 -4
- package/dist/cli/{index-eyzwc7gz.js → index-qbrzvmh9.js} +4 -2
- package/dist/cli/index-qw98g1p6.js +269 -0
- package/dist/cli/{index-434b7j4a.js → index-vtp20gwf.js} +69 -1
- package/dist/cli/{index-h7qhz02e.js → index-wk0pyxrg.js} +3 -0
- package/dist/cli/{index-k3y8cb6c.js → index-z6f43b20.js} +1 -1
- package/dist/cli/index.js +16 -16
- package/dist/cli/{islands-8sgky9gh.js → islands-dc54e5y1.js} +1 -1
- package/dist/cli/{ls-4m4yqwww.js → ls-b6zz24nt.js} +1 -1
- package/dist/cli/{mobile-hcxjr2gf.js → mobile-ajnnvqtv.js} +3 -3
- package/dist/cli/prerender-fqpp0m1j.js +10 -0
- package/dist/cli/{remove-1ycjx85k.js → remove-t8x1mrxg.js} +4 -4
- package/dist/cli/{start-mbaejxhm.js → start-d6p831ny.js} +3 -3
- package/dist/cli/{typecheck-dr068pmh.js → typecheck-stbt0qt5.js} +6 -1
- package/dist/cli/{workspace-1sycp9g1.js → workspace-61hh6jq1.js} +1 -1
- package/dist/client/chunks/index-6b66nzyf.js +381 -0
- package/dist/client/chunks/index-6b66nzyf.js.map +11 -0
- package/dist/client/chunks/{islands-rc28k5sx.js → islands-s2ta5ajb.js} +5 -2
- package/dist/client/chunks/{islands-rc28k5sx.js.map → islands-s2ta5ajb.js.map} +7 -6
- package/dist/client/index.js +160 -3
- package/dist/client/index.js.map +6 -4
- package/dist/client/navigation.js +764 -0
- package/dist/client/navigation.js.map +10 -0
- package/dist/client/prefetch.js +33 -375
- package/dist/client/prefetch.js.map +3 -4
- package/dist/core/streamingSlotRegistrar.js +5 -2
- package/dist/core/streamingSlotRegistrar.js.map +5 -4
- package/dist/core/streamingSlotRegistry.js +5 -2
- package/dist/core/streamingSlotRegistry.js.map +5 -4
- package/dist/dev/client/cspNonce.ts +28 -0
- package/dist/dev/client/handlers/html.ts +4 -3
- package/dist/dev/client/handlers/htmx.ts +3 -2
- package/dist/dev/client/handlers/svelte.ts +15 -90
- package/dist/dev/client/syncDevtools.ts +2 -1
- package/dist/dev/serverBootstrap.js +11 -4
- package/dist/i18n/index.js +543 -0
- package/dist/i18n/index.js.map +15 -0
- package/dist/index.js +6339 -2233
- package/dist/index.js.map +89 -54
- package/dist/islands/index.js +282 -40
- package/dist/islands/index.js.map +14 -10
- package/dist/mobile/browser.js +7 -2
- package/dist/mobile/browser.js.map +5 -4
- package/dist/mobile/index.js +7 -2
- package/dist/mobile/index.js.map +5 -4
- package/dist/mobile/remoteMacAgentEntry.js +1 -1
- package/dist/react/components/browser/index.js +94 -4
- package/dist/react/components/index.js +150 -10
- package/dist/react/components/index.js.map +15 -10
- package/dist/react/hooks/index.js +479 -13
- package/dist/react/hooks/index.js.map +12 -4
- package/dist/react/index.js +769 -166
- package/dist/react/index.js.map +32 -21
- package/dist/react/router/browser.js +21 -4
- package/dist/react/router/browser.js.map +5 -4
- package/dist/react/router/index.js +21 -4
- package/dist/react/router/index.js.map +5 -4
- package/dist/react/server.js +597 -203
- package/dist/react/server.js.map +25 -15
- package/dist/src/angular/zoneRuntime.d.ts +5 -0
- package/dist/src/build/buildReactServerComponents.d.ts +22 -0
- package/dist/src/build/downlevelAsyncForZone.d.ts +1 -0
- package/dist/src/build/fileRoutes.d.ts +65 -0
- package/dist/src/build/fonts/buildFonts.d.ts +25 -0
- package/dist/src/build/fonts/fallbackMetrics.d.ts +13 -0
- package/dist/src/build/fonts/googleFonts.d.ts +18 -0
- package/dist/src/build/fonts/injectFontTags.d.ts +7 -0
- package/dist/src/build/fonts/parseFontMetrics.d.ts +4 -0
- package/dist/src/build/generateReactIndexes.d.ts +7 -1
- package/dist/src/build/htmlIncludes.d.ts +16 -0
- package/dist/src/build/inlineSourceMapComment.d.ts +1 -0
- package/dist/src/build/scanConventions.d.ts +3 -0
- package/dist/src/build/vitals.d.ts +21 -0
- package/dist/src/client/formActions.d.ts +29 -0
- package/dist/src/client/index.d.ts +2 -0
- package/dist/src/client/navigation.d.ts +16 -0
- package/dist/src/client/prefetch.d.ts +2 -30
- package/dist/src/core/build.d.ts +2 -2
- package/dist/src/core/cspNonce.d.ts +12 -0
- package/dist/src/core/fileRouteAdapter.d.ts +15 -0
- package/dist/src/core/fileRoutes.d.ts +5 -0
- package/dist/src/core/formActionFlash.d.ts +25 -0
- package/dist/src/core/formActions.d.ts +249 -0
- package/dist/src/core/index.d.ts +5 -0
- package/dist/src/core/partialPrerender.d.ts +28 -0
- package/dist/src/core/partialPrerenderServe.d.ts +24 -0
- package/dist/src/core/precompressed.d.ts +11 -1
- package/dist/src/core/prerenderNonce.d.ts +9 -0
- package/dist/src/core/productionRuntime.d.ts +4 -2
- package/dist/src/core/serverOnlyStaticFiles.d.ts +8 -0
- package/dist/src/core/staticHtmlNonce.d.ts +2 -0
- package/dist/src/core/vitals.d.ts +108 -0
- package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -1
- package/dist/src/dev/client/cspNonce.d.ts +2 -0
- package/dist/src/dev/fileRouteRefresher.d.ts +5 -0
- package/dist/src/dev/importCost/instrument.d.ts +1 -1
- package/dist/src/dev/reactServerComponentsRebuild.d.ts +3 -0
- package/dist/src/i18n/defineMessages.d.ts +5 -0
- package/dist/src/i18n/formatMessage.d.ts +26 -0
- package/dist/src/i18n/index.d.ts +6 -0
- package/dist/src/i18n/intlCache.d.ts +5 -0
- package/dist/src/i18n/routing.d.ts +28 -0
- package/dist/src/i18n/translator.d.ts +11 -0
- package/dist/src/mobile/androidRelease.d.ts +2 -2
- package/dist/src/mobile/iosRelease.d.ts +4 -4
- package/dist/src/mobile/materializedBundle.d.ts +1 -1
- package/dist/src/mobile/mobileInspect.d.ts +1 -1
- package/dist/src/mobile/releaseArtifact.d.ts +2 -2
- package/dist/src/mobile/remoteMacProtocol.d.ts +2 -2
- package/dist/src/plugins/i18n.d.ts +30 -0
- package/dist/src/plugins/index.d.ts +2 -1
- package/dist/src/plugins/secureHeaders.d.ts +48 -0
- package/dist/src/react/components/Head.d.ts +1 -1
- package/dist/src/react/components/StreamSlot.browser.d.ts +1 -0
- package/dist/src/react/components/StreamSlot.d.ts +5 -1
- package/dist/src/react/components/SuspenseSlot.browser.d.ts +2 -0
- package/dist/src/react/components/SuspenseSlot.d.ts +12 -0
- package/dist/src/react/fileRoute.d.ts +18 -0
- package/dist/src/react/hooks/index.d.ts +4 -0
- package/dist/src/react/hooks/useFormAction.d.ts +42 -0
- package/dist/src/react/hooks/useI18n.d.ts +15 -0
- package/dist/src/react/index.d.ts +2 -0
- package/dist/src/react/server.d.ts +2 -0
- package/dist/src/react/serverComponents/constants.d.ts +11 -0
- package/dist/src/react/serverComponents/flightPayload.d.ts +2 -0
- package/dist/src/react/serverComponents/pageHandler.d.ts +14 -0
- package/dist/src/svelte/browser.d.ts +1 -0
- package/dist/src/svelte/fileRoute.d.ts +15 -0
- package/dist/src/svelte/i18n.d.ts +8 -0
- package/dist/src/svelte/index.d.ts +2 -0
- package/dist/src/svelte/lowerAwaitSlotSyntax.d.ts +5 -1
- package/dist/src/svelte/renderToReadableStream.d.ts +10 -2
- package/dist/src/svelte/router/browser.d.ts +3 -1
- package/dist/src/svelte/router/goto.d.ts +13 -10
- package/dist/src/svelte/router/guards.d.ts +43 -0
- package/dist/src/svelte/router/index.d.ts +3 -1
- package/dist/src/svelte/router/lazy.d.ts +17 -0
- package/dist/src/svelte/router/lazyModule.d.ts +29 -0
- package/dist/src/svelte/router/navigation.d.ts +55 -0
- package/dist/src/svelte/router/pushState.d.ts +4 -2
- package/dist/src/svelte/router/scroll.d.ts +34 -0
- package/dist/src/svelte/routerSsr.d.ts +9 -0
- package/dist/src/svelte/server.d.ts +1 -0
- package/dist/src/utils/cspNonceRef.d.ts +8 -0
- package/dist/src/utils/fontAssets.d.ts +17 -0
- package/dist/src/utils/formActionProtocol.d.ts +10 -0
- package/dist/src/utils/generateHeadElement.d.ts +1 -1
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/inlinePageCss.d.ts +18 -0
- package/dist/src/utils/loadConfig.d.ts +5 -0
- package/dist/src/utils/localeRef.d.ts +10 -0
- package/dist/src/utils/resolveConvention.d.ts +6 -1
- package/dist/src/utils/streamingSlots.d.ts +5 -0
- package/dist/src/vue/browser.d.ts +2 -0
- package/dist/src/vue/components/Image.d.ts +2 -2
- package/dist/src/vue/index.d.ts +2 -0
- package/dist/src/vue/serverDataPayload.d.ts +32 -0
- package/dist/src/vue/serverDataState.d.ts +9 -0
- package/dist/src/vue/useServerData.browser.d.ts +6 -0
- package/dist/src/vue/useServerData.d.ts +24 -0
- package/dist/svelte/browser.js +330 -3
- package/dist/svelte/browser.js.map +8 -4
- package/dist/svelte/components/AwaitSlot.svelte +5 -0
- package/dist/svelte/components/Head.svelte +50 -0
- package/dist/svelte/components/JsonLd.svelte +9 -1
- package/dist/svelte/components/StreamSlot.svelte +5 -0
- package/dist/svelte/index.js +1017 -117
- package/dist/svelte/index.js.map +38 -24
- package/dist/svelte/router/Link.svelte +40 -8
- package/dist/svelte/router/Route.svelte +157 -12
- package/dist/svelte/router/Route.svelte.d.ts +39 -5
- package/dist/svelte/router/Router.svelte +140 -25
- package/dist/svelte/router/Router.svelte.d.ts +5 -1
- package/dist/svelte/router/browser.ts +10 -0
- package/dist/svelte/router/goto.ts +15 -86
- package/dist/svelte/router/guards.ts +112 -0
- package/dist/svelte/router/index.ts +10 -0
- package/dist/svelte/router/lazy.ts +82 -0
- package/dist/svelte/router/lazyModule.ts +74 -0
- package/dist/svelte/router/navigation.ts +412 -0
- package/dist/svelte/router/pushState.ts +7 -4
- package/dist/svelte/router/scroll.ts +286 -0
- package/dist/svelte/server.js +747 -252
- package/dist/svelte/server.js.map +30 -20
- package/dist/types/angular.d.ts +2 -0
- package/dist/types/build.d.ts +37 -0
- package/dist/types/conventions.d.ts +3 -0
- package/dist/types/fileRoutes.d.ts +72 -0
- package/dist/types/fonts.d.ts +86 -0
- package/dist/types/formAction.d.ts +54 -0
- package/dist/types/globals.d.ts +10 -0
- package/dist/types/i18n.d.ts +185 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/metadata.d.ts +11 -0
- package/dist/types/navigation.d.ts +37 -0
- package/dist/types/prefetch.d.ts +10 -0
- package/dist/types/sitemap.d.ts +4 -0
- package/dist/types/svelteRouter.d.ts +89 -2
- package/dist/types/svelteRouter.ts +112 -2
- package/dist/types/vue.d.ts +46 -1
- package/dist/utils/iconVersion.ts +34 -0
- package/dist/vue/browser.js +107 -11
- package/dist/vue/browser.js.map +7 -4
- package/dist/vue/components/index.js +5 -2
- package/dist/vue/components/index.js.map +5 -4
- package/dist/vue/index.js +712 -158
- package/dist/vue/index.js.map +32 -23
- package/dist/vue/server.js +310 -79
- package/dist/vue/server.js.map +20 -15
- package/package.json +9 -1
- package/dist/cli/prerender-mfnwsvyt.js +0 -7
- package/dist/src/plugins/pageRouter.d.ts +0 -1
- /package/dist/cli/{index-j7m8pq48.js → index-rervww4p.js} +0 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy, onMount, type Snippet } from 'svelte';
|
|
3
3
|
import type { LinkPrefetchMode } from '../../types/svelteRouter';
|
|
4
|
-
import { goto } from './goto';
|
|
5
4
|
import {
|
|
5
|
+
belongsToDocumentNavigation,
|
|
6
|
+
navigate,
|
|
7
|
+
preloadRoute
|
|
8
|
+
} from './navigation';
|
|
9
|
+
import {
|
|
10
|
+
canPrefetch,
|
|
6
11
|
observeViewport,
|
|
7
12
|
prefetch,
|
|
8
13
|
resolveDefaultPrefetchMode,
|
|
@@ -26,7 +31,8 @@
|
|
|
26
31
|
prerender?: boolean;
|
|
27
32
|
/** Don't reset focus to body on navigate. */
|
|
28
33
|
keepFocus?: boolean;
|
|
29
|
-
/**
|
|
34
|
+
/** Leave the scroll position alone on navigate (no scroll to the
|
|
35
|
+
* top or to the `#hash` target). */
|
|
30
36
|
noScroll?: boolean;
|
|
31
37
|
/** Forwarded to the underlying `<a>` element. */
|
|
32
38
|
class?: string;
|
|
@@ -77,19 +83,42 @@
|
|
|
77
83
|
if (target && target !== '_self') return;
|
|
78
84
|
if (rest['download'] !== undefined) return;
|
|
79
85
|
if (isExternal(to)) return;
|
|
86
|
+
// No <Router> here has a route for `to` and cross-framework
|
|
87
|
+
// navigation is on: another page owns the URL, so the click goes to
|
|
88
|
+
// the browser, where the navigation layer swaps that page in.
|
|
89
|
+
if (belongsToDocumentNavigation(to)) return;
|
|
80
90
|
|
|
81
91
|
event.preventDefault();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
navigate(
|
|
93
|
+
to,
|
|
94
|
+
{
|
|
95
|
+
keepFocus,
|
|
96
|
+
noScroll,
|
|
97
|
+
replaceState
|
|
98
|
+
},
|
|
99
|
+
'link'
|
|
100
|
+
).catch((navigationError: unknown) => {
|
|
101
|
+
console.error(
|
|
102
|
+
'[absolute] <Link> navigation failed:',
|
|
103
|
+
navigationError
|
|
104
|
+
);
|
|
86
105
|
});
|
|
87
106
|
};
|
|
88
107
|
|
|
108
|
+
// A lazy route's component chunk is part of what a prefetch warms, on
|
|
109
|
+
// the same triggers (hover, viewport, pointerdown) and under the same
|
|
110
|
+
// data-saver / opt-out rules as the document prefetch.
|
|
111
|
+
const preloadLazyRoute = () => {
|
|
112
|
+
if (canPrefetch()) preloadRoute(to);
|
|
113
|
+
};
|
|
114
|
+
|
|
89
115
|
const handlePointerEnter = () => {
|
|
90
116
|
if (resolvedMode() === 'none' || isExternal(to)) return;
|
|
91
117
|
hoverHandle?.cancel();
|
|
92
|
-
hoverHandle = scheduleHoverPrefetch(to, {
|
|
118
|
+
hoverHandle = scheduleHoverPrefetch(to, {
|
|
119
|
+
onPrefetch: preloadLazyRoute,
|
|
120
|
+
prerender
|
|
121
|
+
});
|
|
93
122
|
};
|
|
94
123
|
|
|
95
124
|
const handlePointerLeave = () => {
|
|
@@ -102,6 +131,7 @@
|
|
|
102
131
|
hoverHandle?.cancel();
|
|
103
132
|
hoverHandle = null;
|
|
104
133
|
prefetch(to, { kind: 'route' });
|
|
134
|
+
preloadLazyRoute();
|
|
105
135
|
if (prerender) speculate(to);
|
|
106
136
|
};
|
|
107
137
|
|
|
@@ -109,7 +139,9 @@
|
|
|
109
139
|
if (resolvedMode() !== 'viewport' || !anchor) return;
|
|
110
140
|
if (isExternal(to)) return;
|
|
111
141
|
|
|
112
|
-
unobserveViewport = observeViewport(anchor, to
|
|
142
|
+
unobserveViewport = observeViewport(anchor, to, {
|
|
143
|
+
onVisible: preloadLazyRoute
|
|
144
|
+
});
|
|
113
145
|
});
|
|
114
146
|
|
|
115
147
|
onDestroy(() => {
|
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
<script lang="ts" generics="Path extends string">
|
|
2
2
|
import { getContext, onDestroy, type Snippet } from 'svelte';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ExtractRouteParams,
|
|
5
|
+
LazyRouteLoader,
|
|
6
|
+
RouteGuard,
|
|
7
|
+
SvelteRouterSsrState
|
|
8
|
+
} from '../../types/svelteRouter';
|
|
4
9
|
import type { RouterRegistry } from './Router.svelte';
|
|
10
|
+
import { forgetLazy, lazyCacheKey, loadLazy, peekLazy } from './lazy';
|
|
11
|
+
import type { LazyComponent } from './lazyModule';
|
|
5
12
|
import { compilePattern, joinBasepath } from './matchPath';
|
|
6
13
|
|
|
7
14
|
const ROUTER_CONTEXT_KEY = Symbol.for('absolutejs.svelte-router');
|
|
15
|
+
const ROUTER_SSR_KEY = Symbol.for('absolutejs.svelte-router.ssr');
|
|
8
16
|
|
|
9
17
|
type RouteProps = {
|
|
10
18
|
path: Path;
|
|
11
|
-
|
|
19
|
+
/** Eager route: the snippet to render while the route is active. */
|
|
20
|
+
content?: Snippet<[ExtractRouteParams<Path>]>;
|
|
21
|
+
/** Lazy route: loads the component the first time the route is
|
|
22
|
+
* needed — `() => import('./Page.svelte')`. The build splits it
|
|
23
|
+
* into its own chunk; the server awaits it so the HTML is complete
|
|
24
|
+
* and hydration matches. The component receives the route params
|
|
25
|
+
* (and `props`) as props. */
|
|
26
|
+
loader?: LazyRouteLoader;
|
|
27
|
+
/** Extra props for a lazy route's component. */
|
|
28
|
+
props?: Record<string, unknown>;
|
|
29
|
+
/** Rendered while a lazy route's component loads in the browser. */
|
|
30
|
+
pending?: Snippet;
|
|
31
|
+
/** Rendered when a lazy route's loader rejects. `retry` calls the
|
|
32
|
+
* loader again. */
|
|
33
|
+
error?: Snippet<[unknown, () => void]>;
|
|
34
|
+
/** Runs before navigating onto this route, after the router's
|
|
35
|
+
* guards. Runs during the server render too (a redirect becomes a
|
|
36
|
+
* 302). Return `false` to cancel, a URL or `redirect(...)` to
|
|
37
|
+
* redirect, nothing to allow. */
|
|
38
|
+
guard?: RouteGuard;
|
|
12
39
|
};
|
|
13
40
|
|
|
14
|
-
let {
|
|
41
|
+
let {
|
|
42
|
+
path,
|
|
43
|
+
content,
|
|
44
|
+
loader,
|
|
45
|
+
props: extraProps,
|
|
46
|
+
pending,
|
|
47
|
+
error,
|
|
48
|
+
guard
|
|
49
|
+
}: RouteProps = $props();
|
|
15
50
|
|
|
16
51
|
const registry = getContext<RouterRegistry | undefined>(ROUTER_CONTEXT_KEY);
|
|
17
52
|
if (!registry) {
|
|
@@ -20,25 +55,34 @@
|
|
|
20
55
|
'Wrap your routes in `<Router url={...}>` (server) or `<Router>` (client).'
|
|
21
56
|
);
|
|
22
57
|
}
|
|
58
|
+
const ssr = getContext<SvelteRouterSsrState | undefined>(ROUTER_SSR_KEY);
|
|
23
59
|
|
|
24
60
|
const id = registry.nextRouteId();
|
|
25
61
|
const registrationOrder = Number(id.slice(1));
|
|
62
|
+
const fullPath = $derived(joinBasepath(registry.basepath, path));
|
|
26
63
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
const createEntry = () => ({
|
|
65
|
+
fullPath: joinBasepath(registry.basepath, path),
|
|
66
|
+
getGuard: () => guard,
|
|
67
|
+
getLoader: () => loader,
|
|
30
68
|
pattern: compilePattern(joinBasepath(registry.basepath, path)),
|
|
31
69
|
registrationOrder
|
|
32
70
|
});
|
|
33
71
|
|
|
72
|
+
// Register synchronously at script-body time so SSR (which doesn't
|
|
73
|
+
// run $effect) sees the registration before the template runs.
|
|
74
|
+
registry.register(id, createEntry());
|
|
75
|
+
|
|
34
76
|
$effect(() => {
|
|
35
|
-
registry.register(id,
|
|
36
|
-
pattern: compilePattern(joinBasepath(registry.basepath, path)),
|
|
37
|
-
registrationOrder
|
|
38
|
-
});
|
|
77
|
+
registry.register(id, createEntry());
|
|
39
78
|
});
|
|
40
79
|
|
|
41
|
-
onDestroy
|
|
80
|
+
// The server render runs onDestroy as soon as the HTML is produced, but
|
|
81
|
+
// the router's guards run after that (between render passes) and need
|
|
82
|
+
// the full route table — so only the browser deregisters.
|
|
83
|
+
onDestroy(() => {
|
|
84
|
+
if (typeof window !== 'undefined') registry.deregister(id);
|
|
85
|
+
});
|
|
42
86
|
|
|
43
87
|
// The Router computes the active match across all registered Routes
|
|
44
88
|
// (specificity-ranked). Each Route checks if it's the winner and
|
|
@@ -47,8 +91,109 @@
|
|
|
47
91
|
// that section instead of getting hoisted to the Router's root.
|
|
48
92
|
const match = $derived(registry.getActiveMatch());
|
|
49
93
|
const isActive = $derived(match?.id === id);
|
|
94
|
+
|
|
95
|
+
// ---- Lazy routes ---------------------------------------------------
|
|
96
|
+
//
|
|
97
|
+
// Browser: the component comes from the lazy cache — loaded on an
|
|
98
|
+
// earlier visit, by a <Link> prefetch, or (first hydration) by the
|
|
99
|
+
// hydration entry, which loads the modules the server rendered before
|
|
100
|
+
// it hydrates. Otherwise the effect below loads it while `pending`
|
|
101
|
+
// renders.
|
|
102
|
+
//
|
|
103
|
+
// Server: the first render pass registers the load with the page
|
|
104
|
+
// handler, which awaits it and renders again with the component
|
|
105
|
+
// resolved, so the HTML holds the real component.
|
|
106
|
+
const cacheKey = $derived(loader ? lazyCacheKey(fullPath, loader) : '');
|
|
107
|
+
// Read synchronously at creation so the first (hydrating) render
|
|
108
|
+
// already has the component when it is available.
|
|
109
|
+
const peekInitialComponent = () =>
|
|
110
|
+
!ssr && loader
|
|
111
|
+
? peekLazy(lazyCacheKey(fullPath, loader), fullPath)
|
|
112
|
+
: undefined;
|
|
113
|
+
let loadedComponent = $state<LazyComponent | undefined>(
|
|
114
|
+
peekInitialComponent()
|
|
115
|
+
);
|
|
116
|
+
let loadFailure = $state<{ error: unknown } | undefined>(undefined);
|
|
117
|
+
|
|
118
|
+
const serverLazy = (active: boolean) => {
|
|
119
|
+
if (!ssr || !loader || !active) return undefined;
|
|
120
|
+
const resolved = ssr.resolved.get(fullPath);
|
|
121
|
+
if (!resolved) ssr.load(fullPath, loader);
|
|
122
|
+
// No `error` snippet: fail the render like any other SSR error.
|
|
123
|
+
if (resolved && 'error' in resolved && !error) throw resolved.error;
|
|
124
|
+
|
|
125
|
+
return resolved;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const serverResolved = $derived(serverLazy(isActive));
|
|
129
|
+
const Loaded = $derived(
|
|
130
|
+
ssr
|
|
131
|
+
? serverResolved && 'component' in serverResolved
|
|
132
|
+
? serverResolved.component
|
|
133
|
+
: undefined
|
|
134
|
+
: loadedComponent
|
|
135
|
+
);
|
|
136
|
+
const failure = $derived(
|
|
137
|
+
ssr
|
|
138
|
+
? serverResolved && 'error' in serverResolved
|
|
139
|
+
? { error: serverResolved.error }
|
|
140
|
+
: undefined
|
|
141
|
+
: loadFailure
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const reportLoadFailure = (reason: unknown) => {
|
|
145
|
+
loadFailure = { error: reason };
|
|
146
|
+
// No `error` snippet: surface it like any uncaught error (the dev
|
|
147
|
+
// overlay shows it) instead of failing silently.
|
|
148
|
+
if (!error) {
|
|
149
|
+
queueMicrotask(() => {
|
|
150
|
+
throw reason;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
$effect(() => {
|
|
156
|
+
if (!loader || !isActive || loadFailure) return;
|
|
157
|
+
const key = cacheKey;
|
|
158
|
+
const cached = peekLazy(key, fullPath);
|
|
159
|
+
if (cached) {
|
|
160
|
+
if (loadedComponent !== cached) loadedComponent = cached;
|
|
161
|
+
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (loadedComponent) loadedComponent = undefined;
|
|
165
|
+
let live = true;
|
|
166
|
+
loadLazy(key, loader).then(
|
|
167
|
+
(component) => {
|
|
168
|
+
if (live) loadedComponent = component;
|
|
169
|
+
},
|
|
170
|
+
(reason: unknown) => {
|
|
171
|
+
if (live) reportLoadFailure(reason);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return () => {
|
|
176
|
+
live = false;
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const retry = () => {
|
|
181
|
+
forgetLazy(cacheKey);
|
|
182
|
+
loadFailure = undefined;
|
|
183
|
+
loadedComponent = undefined;
|
|
184
|
+
};
|
|
50
185
|
</script>
|
|
51
186
|
|
|
52
187
|
{#if isActive && match}
|
|
53
|
-
{
|
|
188
|
+
{#if loader}
|
|
189
|
+
{#if Loaded}
|
|
190
|
+
<Loaded {...extraProps ?? {}} {...match.params} />
|
|
191
|
+
{:else if failure}
|
|
192
|
+
{@render error?.(failure.error, retry)}
|
|
193
|
+
{:else if pending}
|
|
194
|
+
{@render pending()}
|
|
195
|
+
{/if}
|
|
196
|
+
{:else if content}
|
|
197
|
+
{@render content(match.params as ExtractRouteParams<Path>)}
|
|
198
|
+
{/if}
|
|
54
199
|
{/if}
|
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import { SvelteComponent, type Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ExtractRouteParams,
|
|
4
|
+
LazyRouteLoader,
|
|
5
|
+
RouteGuard
|
|
6
|
+
} from '../../types/svelteRouter';
|
|
3
7
|
|
|
4
|
-
type
|
|
8
|
+
type RouteBaseProps<Path extends string> = {
|
|
5
9
|
path: Path;
|
|
10
|
+
/** Runs before navigating onto this route, after the router's guards
|
|
11
|
+
* (and during the server render, where a redirect becomes a 302). */
|
|
12
|
+
guard?: RouteGuard;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Eager route: render a snippet while the route is active. */
|
|
16
|
+
type EagerRouteProps<Path extends string> = RouteBaseProps<Path> & {
|
|
6
17
|
content: Snippet<[ExtractRouteParams<Path>]>;
|
|
18
|
+
loader?: never;
|
|
19
|
+
props?: never;
|
|
20
|
+
pending?: never;
|
|
21
|
+
error?: never;
|
|
7
22
|
};
|
|
8
23
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
/** Lazy route: load the component on first use —
|
|
25
|
+
* `loader={() => import('./Page.svelte')}`. It receives the route params
|
|
26
|
+
* (plus `props`) as props. */
|
|
27
|
+
type LazyRouteProps<
|
|
28
|
+
Path extends string,
|
|
29
|
+
Props extends Record<string, unknown>
|
|
30
|
+
> = RouteBaseProps<Path> & {
|
|
31
|
+
content?: never;
|
|
32
|
+
loader: LazyRouteLoader<Props>;
|
|
33
|
+
props?: Omit<Props, keyof ExtractRouteParams<Path>>;
|
|
34
|
+
pending?: Snippet;
|
|
35
|
+
error?: Snippet<[unknown, () => void]>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type RouteProps<Path extends string, Props extends Record<string, unknown>> =
|
|
39
|
+
| EagerRouteProps<Path>
|
|
40
|
+
| LazyRouteProps<Path, Props>;
|
|
41
|
+
|
|
42
|
+
class Route<
|
|
43
|
+
Path extends string = string,
|
|
44
|
+
Props extends Record<string, unknown> = ExtractRouteParams<Path>
|
|
45
|
+
> extends SvelteComponent<RouteProps<Path, Props>> {}
|
|
12
46
|
|
|
13
47
|
export { Route as default };
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
type RouteEntry = {
|
|
3
3
|
// reactive — read by the winner-resolver
|
|
4
4
|
pattern: ReturnType<typeof import('./matchPath').compilePattern>;
|
|
5
|
+
// basepath-joined path pattern, the lazy-route cache key
|
|
6
|
+
fullPath: string;
|
|
5
7
|
// stable — assigned at registration time, used as tiebreaker
|
|
6
8
|
registrationOrder: number;
|
|
9
|
+
// read on demand so a changed prop is always seen
|
|
10
|
+
getGuard: () =>
|
|
11
|
+
| import('../../types/svelteRouter').RouteGuard
|
|
12
|
+
| undefined;
|
|
13
|
+
getLoader: () =>
|
|
14
|
+
| import('../../types/svelteRouter').LazyRouteLoader
|
|
15
|
+
| undefined;
|
|
7
16
|
};
|
|
8
17
|
|
|
9
18
|
type ActiveMatch = {
|
|
@@ -27,13 +36,27 @@
|
|
|
27
36
|
|
|
28
37
|
<script lang="ts">
|
|
29
38
|
import { getContext, onMount, setContext, type Snippet } from 'svelte';
|
|
30
|
-
import type {
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
import type {
|
|
40
|
+
RouteGuard,
|
|
41
|
+
RouterMode,
|
|
42
|
+
SvelteRouterSsrState
|
|
43
|
+
} from '../../types/svelteRouter';
|
|
44
|
+
import { type GuardEntry, runGuardChain } from './guards';
|
|
45
|
+
import { buildHashHref, hashPathnameOf } from './hashMode';
|
|
46
|
+
import { lazyCacheKey, loadLazy } from './lazy';
|
|
33
47
|
import { joinBasepath, matchPattern } from './matchPath';
|
|
34
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
enterInitialRoute,
|
|
50
|
+
handleTraversal,
|
|
51
|
+
registerRouter,
|
|
52
|
+
restoreInitialScroll,
|
|
53
|
+
setRouterMode
|
|
54
|
+
} from './navigation';
|
|
55
|
+
import { page, seedPage } from './page.js';
|
|
56
|
+
import { activateScrollRestoration, stampInitialEntry } from './scroll';
|
|
35
57
|
|
|
36
58
|
const ROUTER_CONTEXT_KEY = Symbol.for('absolutejs.svelte-router');
|
|
59
|
+
const ROUTER_SSR_KEY = Symbol.for('absolutejs.svelte-router.ssr');
|
|
37
60
|
|
|
38
61
|
type RouterProps = {
|
|
39
62
|
/** SSR URL passthrough. On the server, the page handler forwards
|
|
@@ -46,6 +69,12 @@
|
|
|
46
69
|
/** `'history'` (default, clean URLs) or `'hash'` (`/#/path`,
|
|
47
70
|
* for static deploys). */
|
|
48
71
|
mode?: RouterMode;
|
|
72
|
+
/** Runs before every navigation onto this router's routes (the
|
|
73
|
+
* outermost router: every navigation), before the matched
|
|
74
|
+
* `<Route guard>`. Runs during the server render too, where a
|
|
75
|
+
* redirect becomes a 302. Return `false` to cancel, a URL or
|
|
76
|
+
* `redirect(...)` to redirect, nothing to allow. */
|
|
77
|
+
guard?: RouteGuard;
|
|
49
78
|
children?: Snippet;
|
|
50
79
|
};
|
|
51
80
|
|
|
@@ -53,10 +82,12 @@
|
|
|
53
82
|
url,
|
|
54
83
|
basepath = '',
|
|
55
84
|
mode = 'history',
|
|
85
|
+
guard,
|
|
56
86
|
children
|
|
57
87
|
}: RouterProps = $props();
|
|
58
88
|
|
|
59
89
|
const parent = getContext<RouterRegistry | undefined>(ROUTER_CONTEXT_KEY);
|
|
90
|
+
const ssr = getContext<SvelteRouterSsrState | undefined>(ROUTER_SSR_KEY);
|
|
60
91
|
const stackedBasepath = parent
|
|
61
92
|
? joinBasepath(parent.basepath, basepath)
|
|
62
93
|
: basepath === ''
|
|
@@ -77,14 +108,14 @@
|
|
|
77
108
|
const routes = $state(new Map<string, RouteEntry>());
|
|
78
109
|
let routeCounter = 0;
|
|
79
110
|
|
|
80
|
-
const
|
|
111
|
+
const computeWinnerFor = (pathname: string): ActiveMatch | null => {
|
|
81
112
|
let bestId: string | null = null;
|
|
82
113
|
let bestParams: Record<string, string | undefined> | null = null;
|
|
83
114
|
let bestScore = -Infinity;
|
|
84
115
|
let bestOrder = Infinity;
|
|
85
116
|
|
|
86
117
|
for (const [id, entry] of routes) {
|
|
87
|
-
const match = matchPattern(entry.pattern,
|
|
118
|
+
const match = matchPattern(entry.pattern, pathname);
|
|
88
119
|
if (!match.matched) continue;
|
|
89
120
|
|
|
90
121
|
if (
|
|
@@ -118,7 +149,7 @@
|
|
|
118
149
|
deregister: (id) => {
|
|
119
150
|
routes.delete(id);
|
|
120
151
|
},
|
|
121
|
-
getActiveMatch: () =>
|
|
152
|
+
getActiveMatch: () => computeWinnerFor(page.url.pathname),
|
|
122
153
|
mode: stackedMode,
|
|
123
154
|
nextRouteId: () => `r${routeCounter++}`,
|
|
124
155
|
register: (id, entry) => {
|
|
@@ -128,6 +159,39 @@
|
|
|
128
159
|
|
|
129
160
|
setContext<RouterRegistry>(ROUTER_CONTEXT_KEY, registry);
|
|
130
161
|
|
|
162
|
+
// Guards for a navigation to `target`: this router's own guard (the
|
|
163
|
+
// outermost one always; a nested one only when one of its routes
|
|
164
|
+
// matches), then the matched route's guard.
|
|
165
|
+
const guardsFor = (target: URL) => {
|
|
166
|
+
const winner = computeWinnerFor(target.pathname);
|
|
167
|
+
const entries: GuardEntry[] = [];
|
|
168
|
+
const routerGuard = guard;
|
|
169
|
+
if (routerGuard && (isOutermost || winner)) {
|
|
170
|
+
entries.push({ guard: routerGuard, params: winner?.params ?? {} });
|
|
171
|
+
}
|
|
172
|
+
const routeGuard = winner
|
|
173
|
+
? routes.get(winner.id)?.getGuard()
|
|
174
|
+
: undefined;
|
|
175
|
+
if (winner && routeGuard) {
|
|
176
|
+
entries.push({ guard: routeGuard, params: winner.params });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return entries;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const matches = (target: URL) => computeWinnerFor(target.pathname) !== null;
|
|
183
|
+
|
|
184
|
+
const preload = (target: URL) => {
|
|
185
|
+
const winner = computeWinnerFor(target.pathname);
|
|
186
|
+
const entry = winner ? routes.get(winner.id) : undefined;
|
|
187
|
+
const loader = entry?.getLoader();
|
|
188
|
+
if (!entry || !loader) return;
|
|
189
|
+
loadLazy(lazyCacheKey(entry.fullPath, loader), loader).catch(() => {
|
|
190
|
+
// A failed prefetch is retried (and reported) by the Route
|
|
191
|
+
// itself if the user actually navigates there.
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
131
195
|
if (isOutermost) {
|
|
132
196
|
setRouterMode(stackedMode);
|
|
133
197
|
|
|
@@ -145,31 +209,80 @@
|
|
|
145
209
|
seedPage(initial);
|
|
146
210
|
}
|
|
147
211
|
|
|
212
|
+
// Server render: once this pass has registered every Route, run this
|
|
213
|
+
// router's guards for the request URL. The page handler awaits them
|
|
214
|
+
// before rendering again; a redirect becomes a 302. The URL is captured
|
|
215
|
+
// now — `page` is shared module state and other requests may render
|
|
216
|
+
// before the guards run.
|
|
217
|
+
if (ssr) {
|
|
218
|
+
const renderUrl = new URL(page.url.href);
|
|
219
|
+
const requestUrl = new URL(url ?? '/', 'http://localhost/');
|
|
220
|
+
const guardKey = `${stackedBasepath}\u0000${renderUrl.href}`;
|
|
221
|
+
ssr.afterRender(async () => {
|
|
222
|
+
if (ssr.outcome() !== undefined) return;
|
|
223
|
+
if (ssr.settledGuards.has(guardKey)) return;
|
|
224
|
+
ssr.settledGuards.add(guardKey);
|
|
225
|
+
const decision = await runGuardChain(guardsFor(renderUrl), {
|
|
226
|
+
from: null,
|
|
227
|
+
request: ssr.request,
|
|
228
|
+
to: renderUrl,
|
|
229
|
+
type: 'enter'
|
|
230
|
+
});
|
|
231
|
+
if (decision.kind === 'cancel') ssr.setOutcome({ kind: 'cancel' });
|
|
232
|
+
if (decision.kind !== 'redirect') return;
|
|
233
|
+
const target = new URL(decision.location, renderUrl);
|
|
234
|
+
const location =
|
|
235
|
+
stackedMode === 'hash'
|
|
236
|
+
? `${requestUrl.pathname}${requestUrl.search}${buildHashHref(target.pathname + target.search)}`
|
|
237
|
+
: target.origin === renderUrl.origin
|
|
238
|
+
? `${target.pathname}${target.search}${target.hash}`
|
|
239
|
+
: target.href;
|
|
240
|
+
ssr.setOutcome({ kind: 'redirect', location });
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
148
244
|
onMount(() => {
|
|
149
|
-
|
|
245
|
+
const unregister = registerRouter({ guardsFor, matches, preload });
|
|
246
|
+
if (!isOutermost) return unregister;
|
|
150
247
|
|
|
151
|
-
const
|
|
248
|
+
const initialEntry = stampInitialEntry();
|
|
249
|
+
const deactivateScroll = activateScrollRestoration();
|
|
250
|
+
const navigationEntry = performance.getEntriesByType('navigation')[0];
|
|
251
|
+
const navigationKind =
|
|
252
|
+
navigationEntry && 'type' in navigationEntry
|
|
253
|
+
? navigationEntry.type
|
|
254
|
+
: undefined;
|
|
255
|
+
if (
|
|
256
|
+
initialEntry.scroll &&
|
|
257
|
+
(navigationKind === 'reload' || navigationKind === 'back_forward')
|
|
258
|
+
) {
|
|
259
|
+
void restoreInitialScroll(initialEntry.scroll);
|
|
260
|
+
}
|
|
261
|
+
if (stackedMode === 'hash') void enterInitialRoute(page.url);
|
|
262
|
+
|
|
263
|
+
const routableUrl = () => {
|
|
152
264
|
const next = new URL(window.location.href);
|
|
153
|
-
const routable =
|
|
154
|
-
stackedMode === 'hash' ? hashPathnameOf(next) : next.pathname;
|
|
155
265
|
const synthetic = new URL(next.href);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
url: synthetic
|
|
161
|
-
});
|
|
266
|
+
if (stackedMode === 'hash')
|
|
267
|
+
synthetic.pathname = hashPathnameOf(next);
|
|
268
|
+
|
|
269
|
+
return synthetic;
|
|
162
270
|
};
|
|
163
271
|
|
|
272
|
+
let lastTraversalHref = '';
|
|
273
|
+
const onPopState = (event: PopStateEvent) => {
|
|
274
|
+
lastTraversalHref = window.location.href;
|
|
275
|
+
void handleTraversal(event.state, routableUrl());
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Browsers fire `popstate` for hash changes too; `hashchange` only
|
|
279
|
+
// catches the rare engine that doesn't, so skip URLs already handled.
|
|
164
280
|
const onHashChange = () => {
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
state: window.history.state ?? null,
|
|
171
|
-
url: synthetic
|
|
172
|
-
});
|
|
281
|
+
if (window.location.href === lastTraversalHref) return;
|
|
282
|
+
const next = routableUrl();
|
|
283
|
+
if (next.href === page.url.href) return;
|
|
284
|
+
lastTraversalHref = window.location.href;
|
|
285
|
+
void handleTraversal(window.history.state, next);
|
|
173
286
|
};
|
|
174
287
|
|
|
175
288
|
window.addEventListener('popstate', onPopState);
|
|
@@ -178,6 +291,8 @@
|
|
|
178
291
|
}
|
|
179
292
|
|
|
180
293
|
return () => {
|
|
294
|
+
unregister();
|
|
295
|
+
deactivateScroll();
|
|
181
296
|
window.removeEventListener('popstate', onPopState);
|
|
182
297
|
if (stackedMode === 'hash') {
|
|
183
298
|
window.removeEventListener('hashchange', onHashChange);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { SvelteComponent, type Snippet } from 'svelte';
|
|
2
|
-
import type { RouterMode } from '../../types/svelteRouter';
|
|
2
|
+
import type { RouteGuard, RouterMode } from '../../types/svelteRouter';
|
|
3
3
|
|
|
4
4
|
type RouterProps = {
|
|
5
5
|
url?: string;
|
|
6
6
|
basepath?: string;
|
|
7
7
|
mode?: RouterMode;
|
|
8
|
+
/** Runs before every navigation onto this router's routes (the
|
|
9
|
+
* outermost router: every navigation), and during the server render,
|
|
10
|
+
* where a redirect becomes a 302. */
|
|
11
|
+
guard?: RouteGuard;
|
|
8
12
|
children?: Snippet;
|
|
9
13
|
};
|
|
10
14
|
|
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
// their .svelte path; this entry exposes only the runtime API.
|
|
3
3
|
|
|
4
4
|
export { goto } from './goto';
|
|
5
|
+
export { NavigationRedirect, redirect } from './guards';
|
|
6
|
+
export { beforeNavigate, preloadRoute } from './navigation';
|
|
5
7
|
export { page } from './page.js';
|
|
6
8
|
export { pushState, replaceState } from './pushState';
|
|
7
9
|
|
|
8
10
|
export type {
|
|
9
11
|
ExtractRouteParams,
|
|
10
12
|
GotoOptions,
|
|
13
|
+
LazyRouteLoader,
|
|
14
|
+
LazyRouteModule,
|
|
11
15
|
LinkPrefetchMode,
|
|
16
|
+
NavigationContext,
|
|
17
|
+
NavigationOutcome,
|
|
18
|
+
NavigationRedirectTarget,
|
|
19
|
+
NavigationType,
|
|
12
20
|
PageState,
|
|
21
|
+
RouteGuard,
|
|
22
|
+
RouteGuardResult,
|
|
13
23
|
RouterMode
|
|
14
24
|
} from '../../types/svelteRouter';
|