@pyreon/router 0.12.12 → 0.12.14
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/README.md +14 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +53 -30
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +13 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/router.ts +107 -33
- package/src/scroll.ts +6 -1
- package/src/tests/loader.test.ts +52 -2
- package/src/tests/router.browser.test.tsx +442 -0
- package/src/tests/router.test.ts +253 -8
- package/src/types.ts +13 -0
package/lib/types/index.d.ts
CHANGED
|
@@ -242,6 +242,19 @@ interface Router<TNames extends string = string> {
|
|
|
242
242
|
* Useful for SSR and for delaying rendering until the first route is resolved.
|
|
243
243
|
*/
|
|
244
244
|
isReady(): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Resolve `path` and prepare everything needed to render it: load any lazy
|
|
247
|
+
* route components into the router's cache and run the matched routes'
|
|
248
|
+
* loaders. After this resolves, a `RouterView` rendered against this router
|
|
249
|
+
* for `path` will produce final HTML synchronously — no loading fallbacks,
|
|
250
|
+
* no `useLoaderData()` returning `undefined`.
|
|
251
|
+
*
|
|
252
|
+
* Used by SSR/SSG to hydrate the route tree before `renderToString`.
|
|
253
|
+
* The router's `currentRoute` is NOT changed by `preload` — pass the path
|
|
254
|
+
* separately when creating the router (`createRouter({ url, ... })`) or
|
|
255
|
+
* call this for the same `url` you initialised the router with.
|
|
256
|
+
*/
|
|
257
|
+
preload(path: string): Promise<void>;
|
|
245
258
|
/** Remove all event listeners, clear caches, and abort in-flight navigations. */
|
|
246
259
|
destroy(): void;
|
|
247
260
|
}
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/components.tsx","../../../src/loader.ts","../../../src/match.ts","../../../src/router.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/components.tsx","../../../src/loader.ts","../../../src/match.ts","../../../src/router.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/router",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.14",
|
|
4
4
|
"description": "Official router for Pyreon",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/router#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -37,17 +37,20 @@
|
|
|
37
37
|
"build": "vl_rolldown_build",
|
|
38
38
|
"dev": "vl_rolldown_build-watch",
|
|
39
39
|
"test": "vitest run",
|
|
40
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
40
41
|
"typecheck": "tsc --noEmit",
|
|
41
42
|
"lint": "oxlint .",
|
|
42
43
|
"prepublishOnly": "bun run build"
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
|
-
"@pyreon/core": "^0.12.
|
|
46
|
-
"@pyreon/reactivity": "^0.12.
|
|
47
|
-
"@pyreon/runtime-dom": "^0.12.
|
|
46
|
+
"@pyreon/core": "^0.12.14",
|
|
47
|
+
"@pyreon/reactivity": "^0.12.14",
|
|
48
|
+
"@pyreon/runtime-dom": "^0.12.14"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
52
|
+
"@pyreon/test-utils": "^0.12.10",
|
|
53
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
51
54
|
"happy-dom": "^20.8.3"
|
|
52
55
|
}
|
|
53
56
|
}
|
package/src/router.ts
CHANGED
|
@@ -56,7 +56,7 @@ export function useRouter(): Router {
|
|
|
56
56
|
const router = useContext(RouterContext) ?? _activeRouter
|
|
57
57
|
if (!router)
|
|
58
58
|
throw new Error(
|
|
59
|
-
'[
|
|
59
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
60
60
|
)
|
|
61
61
|
return router
|
|
62
62
|
}
|
|
@@ -68,7 +68,7 @@ export function useRoute<TPath extends string = string>(): () => ResolvedRoute<
|
|
|
68
68
|
const router = useContext(RouterContext) ?? _activeRouter
|
|
69
69
|
if (!router)
|
|
70
70
|
throw new Error(
|
|
71
|
-
'[
|
|
71
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
72
72
|
)
|
|
73
73
|
return router.currentRoute as never
|
|
74
74
|
}
|
|
@@ -87,7 +87,7 @@ export function onBeforeRouteLeave(guard: NavigationGuard): () => void {
|
|
|
87
87
|
const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null
|
|
88
88
|
if (!router)
|
|
89
89
|
throw new Error(
|
|
90
|
-
'[
|
|
90
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
91
91
|
)
|
|
92
92
|
// Register as a global guard that only fires when leaving the current route
|
|
93
93
|
const currentMatched = router.currentRoute().matched
|
|
@@ -116,7 +116,7 @@ export function onBeforeRouteUpdate(guard: NavigationGuard): () => void {
|
|
|
116
116
|
const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null
|
|
117
117
|
if (!router)
|
|
118
118
|
throw new Error(
|
|
119
|
-
'[
|
|
119
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
120
120
|
)
|
|
121
121
|
const currentMatched = router.currentRoute().matched
|
|
122
122
|
const wrappedGuard: NavigationGuard = (to, from) => {
|
|
@@ -148,7 +148,7 @@ export function useBlocker(fn: BlockerFn): Blocker {
|
|
|
148
148
|
const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null
|
|
149
149
|
if (!router)
|
|
150
150
|
throw new Error(
|
|
151
|
-
'[
|
|
151
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
152
152
|
)
|
|
153
153
|
router._blockers.add(fn)
|
|
154
154
|
|
|
@@ -207,7 +207,7 @@ export function useIsActive(path: string, exact = false): () => boolean {
|
|
|
207
207
|
const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null
|
|
208
208
|
if (!router)
|
|
209
209
|
throw new Error(
|
|
210
|
-
'[
|
|
210
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
211
211
|
)
|
|
212
212
|
return () => {
|
|
213
213
|
const current = router.currentRoute().path
|
|
@@ -331,7 +331,7 @@ function _getRouter(): RouterInstance {
|
|
|
331
331
|
const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null
|
|
332
332
|
if (!router)
|
|
333
333
|
throw new Error(
|
|
334
|
-
'[
|
|
334
|
+
'[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',
|
|
335
335
|
)
|
|
336
336
|
return router
|
|
337
337
|
}
|
|
@@ -428,19 +428,17 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
428
428
|
const currentPath = signal(normalizeTrailingSlash(getInitialLocation(), trailingSlash))
|
|
429
429
|
const currentRoute = computed<ResolvedRoute>(() => resolveRoute(currentPath(), routes))
|
|
430
430
|
|
|
431
|
-
// Browser event listeners — stored so destroy() can remove them
|
|
432
|
-
|
|
433
|
-
|
|
431
|
+
// Browser event listeners — stored so destroy() can remove them.
|
|
432
|
+
// Ternary-bound on `_isBrowser` (a typeof-derived const) so the lint rule
|
|
433
|
+
// can trace these to an SSR-safe shape without needing `if (_isBrowser &&
|
|
434
|
+
// handler)` contortions at every use site.
|
|
435
|
+
const _popstateHandler: (() => void) | null =
|
|
436
|
+
_isBrowser && mode === 'history' ? () => currentPath.set(getCurrentLocation()) : null
|
|
437
|
+
const _hashchangeHandler: (() => void) | null =
|
|
438
|
+
_isBrowser && mode !== 'history' ? () => currentPath.set(getCurrentLocation()) : null
|
|
434
439
|
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
_popstateHandler = () => currentPath.set(getCurrentLocation())
|
|
438
|
-
window.addEventListener('popstate', _popstateHandler)
|
|
439
|
-
} else {
|
|
440
|
-
_hashchangeHandler = () => currentPath.set(getCurrentLocation())
|
|
441
|
-
window.addEventListener('hashchange', _hashchangeHandler)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
440
|
+
if (_popstateHandler) window.addEventListener('popstate', _popstateHandler)
|
|
441
|
+
if (_hashchangeHandler) window.addEventListener('hashchange', _hashchangeHandler)
|
|
444
442
|
|
|
445
443
|
const componentCache = new Map<RouteRecord, ComponentFn>()
|
|
446
444
|
const loadingSignal = signal(0)
|
|
@@ -608,12 +606,12 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
608
606
|
return true
|
|
609
607
|
}
|
|
610
608
|
|
|
611
|
-
function commitNavigation(
|
|
609
|
+
async function commitNavigation(
|
|
612
610
|
path: string,
|
|
613
611
|
replace: boolean,
|
|
614
612
|
to: ResolvedRoute,
|
|
615
613
|
from: ResolvedRoute,
|
|
616
|
-
): void {
|
|
614
|
+
): Promise<void> {
|
|
617
615
|
scrollManager.save(from.path)
|
|
618
616
|
|
|
619
617
|
const doCommit = () => {
|
|
@@ -638,9 +636,54 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
638
636
|
&& typeof (document as any).startViewTransition === 'function'
|
|
639
637
|
|
|
640
638
|
if (useVT) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
639
|
+
// `startViewTransition(cb)` runs `cb` inside an async transition. Its
|
|
640
|
+
// `.updateCallbackDone` promise resolves as soon as the callback
|
|
641
|
+
// finishes — DOM has swapped, state is live, but the fade/slide
|
|
642
|
+
// animation is still running. That's what `await router.push()`
|
|
643
|
+
// should wait for: callers need the new route live before they act
|
|
644
|
+
// (e.g. focus an element, inspect `location`, query a new DOM node);
|
|
645
|
+
// they don't want to block on the full animation (`.finished`),
|
|
646
|
+
// which would add 200-300ms to every programmatic navigation.
|
|
647
|
+
//
|
|
648
|
+
// Before this await, `commitNavigation` was sync: the transition
|
|
649
|
+
// callback ran in a later microtask, so `await router.push()`
|
|
650
|
+
// resolved BEFORE the DOM swap. Browser smoke tests had to opt out
|
|
651
|
+
// of View Transitions per-route via `meta: { viewTransition: false }`
|
|
652
|
+
// to stay deterministic — a flag whose only purpose was to paper
|
|
653
|
+
// over this bug.
|
|
654
|
+
type ViewTransitionLike = {
|
|
655
|
+
updateCallbackDone?: Promise<void>
|
|
656
|
+
ready?: Promise<void>
|
|
657
|
+
finished?: Promise<void>
|
|
658
|
+
}
|
|
659
|
+
const vt = (document as { startViewTransition?: (cb: () => void) => ViewTransitionLike | undefined })
|
|
660
|
+
.startViewTransition!(() => {
|
|
661
|
+
doCommit()
|
|
662
|
+
})
|
|
663
|
+
// `startViewTransition` may return `undefined` in test doubles
|
|
664
|
+
// that shim it with a bare `(cb) => cb()`. Guard accordingly.
|
|
665
|
+
if (vt) {
|
|
666
|
+
// The ViewTransition object exposes THREE promises —
|
|
667
|
+
// `updateCallbackDone`, `ready`, `finished`. When a newer
|
|
668
|
+
// `startViewTransition()` starts while this one is in flight,
|
|
669
|
+
// `ready` and `finished` reject with `AbortError: Transition
|
|
670
|
+
// was skipped`. We only need to wait on `updateCallbackDone`
|
|
671
|
+
// (the DOM-commit signal), but the other two MUST still be
|
|
672
|
+
// handled or the rejection surfaces as an unhandled promise
|
|
673
|
+
// rejection that breaks test runners and CI dashboards.
|
|
674
|
+
vt.ready?.catch(() => {})
|
|
675
|
+
vt.finished?.catch(() => {})
|
|
676
|
+
if (vt.updateCallbackDone) {
|
|
677
|
+
try {
|
|
678
|
+
await vt.updateCallbackDone
|
|
679
|
+
} catch {
|
|
680
|
+
// `updateCallbackDone` rejects if the callback itself throws.
|
|
681
|
+
// The DOM may be in a partial-commit state; the newer
|
|
682
|
+
// navigation (if any) will re-commit. Swallow so the
|
|
683
|
+
// navigation chain never hangs on a transition error.
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
644
687
|
} else {
|
|
645
688
|
doCommit()
|
|
646
689
|
}
|
|
@@ -756,7 +799,7 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
756
799
|
return
|
|
757
800
|
}
|
|
758
801
|
|
|
759
|
-
commitNavigation(path, replace, to, from)
|
|
802
|
+
await commitNavigation(path, replace, to, from)
|
|
760
803
|
loadingSignal.update((n) => n - 1)
|
|
761
804
|
}
|
|
762
805
|
|
|
@@ -861,15 +904,46 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
861
904
|
return router._readyPromise
|
|
862
905
|
},
|
|
863
906
|
|
|
907
|
+
async preload(path: string) {
|
|
908
|
+
const resolved = resolveRoute(path, routes)
|
|
909
|
+
// Load lazy components in parallel and populate the component cache so
|
|
910
|
+
// the synchronous render pass finds ready components instead of kicking
|
|
911
|
+
// off async imports (which would fall back to loadingComponent).
|
|
912
|
+
await Promise.all(
|
|
913
|
+
resolved.matched.map(async (record) => {
|
|
914
|
+
if (componentCache.has(record)) return
|
|
915
|
+
const raw = record.component
|
|
916
|
+
if (!isLazy(raw)) {
|
|
917
|
+
componentCache.set(record, raw)
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
const mod = await raw.loader()
|
|
921
|
+
const comp = typeof mod === 'function' ? mod : mod.default
|
|
922
|
+
componentCache.set(record, comp)
|
|
923
|
+
}),
|
|
924
|
+
)
|
|
925
|
+
// Run loaders for the matched path — uses the same code path SSR
|
|
926
|
+
// already relied on, so loader data ends up in `_loaderData` under the
|
|
927
|
+
// matched route records.
|
|
928
|
+
const ac = new AbortController()
|
|
929
|
+
router._abortController = ac
|
|
930
|
+
await Promise.all(
|
|
931
|
+
resolved.matched
|
|
932
|
+
.filter((r) => r.loader)
|
|
933
|
+
.map(async (r) => {
|
|
934
|
+
const data = await r.loader?.({
|
|
935
|
+
params: resolved.params,
|
|
936
|
+
query: resolved.query,
|
|
937
|
+
signal: ac.signal,
|
|
938
|
+
})
|
|
939
|
+
router._loaderData.set(r, data)
|
|
940
|
+
}),
|
|
941
|
+
)
|
|
942
|
+
},
|
|
943
|
+
|
|
864
944
|
destroy() {
|
|
865
|
-
if (_popstateHandler)
|
|
866
|
-
|
|
867
|
-
_popstateHandler = null
|
|
868
|
-
}
|
|
869
|
-
if (_hashchangeHandler) {
|
|
870
|
-
window.removeEventListener('hashchange', _hashchangeHandler)
|
|
871
|
-
_hashchangeHandler = null
|
|
872
|
-
}
|
|
945
|
+
if (_popstateHandler) window.removeEventListener('popstate', _popstateHandler)
|
|
946
|
+
if (_hashchangeHandler) window.removeEventListener('hashchange', _hashchangeHandler)
|
|
873
947
|
guards.length = 0
|
|
874
948
|
afterHooks.length = 0
|
|
875
949
|
router._blockers.clear()
|
package/src/scroll.ts
CHANGED
|
@@ -16,7 +16,11 @@ export class ScrollManager {
|
|
|
16
16
|
|
|
17
17
|
/** Call before navigating away — saves current scroll position for `fromPath` */
|
|
18
18
|
save(fromPath: string): void {
|
|
19
|
-
//
|
|
19
|
+
// ScrollManager methods are only invoked from browser navigation paths,
|
|
20
|
+
// but an explicit early-return documents the SSR-safety contract at the
|
|
21
|
+
// callsite (the `no-window-in-ssr` lint rule can't AST-trace indirect
|
|
22
|
+
// calls from router setup).
|
|
23
|
+
if (typeof window === 'undefined') return
|
|
20
24
|
this._positions.set(fromPath, window.scrollY)
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -35,6 +39,7 @@ export class ScrollManager {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
private _applyResult(result: 'top' | 'restore' | 'none' | number, toPath: string): void {
|
|
42
|
+
if (typeof window === 'undefined') return
|
|
38
43
|
// Hash scrolling: if the path contains #, scroll to the element
|
|
39
44
|
const hashIdx = toPath.indexOf('#')
|
|
40
45
|
if (hashIdx >= 0) {
|
package/src/tests/loader.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hydrateLoaderData, prefetchLoaderData, serializeLoaderData } from '../loader'
|
|
2
2
|
import { createRouter, setActiveRouter, useIsActive, useSearchParams } from '../router'
|
|
3
|
+
import { lazy } from '../types'
|
|
3
4
|
import type { RouteRecord, RouterInstance } from '../types'
|
|
4
5
|
|
|
5
6
|
const Home = () => null
|
|
@@ -119,7 +120,7 @@ describe('useIsActive — edge cases', () => {
|
|
|
119
120
|
})
|
|
120
121
|
|
|
121
122
|
test('throws when no router installed', () => {
|
|
122
|
-
expect(() => useIsActive('/')).toThrow('[
|
|
123
|
+
expect(() => useIsActive('/')).toThrow('[Pyreon] No router installed')
|
|
123
124
|
})
|
|
124
125
|
|
|
125
126
|
test('exact match for root path', () => {
|
|
@@ -227,7 +228,7 @@ describe('useSearchParams — edge cases', () => {
|
|
|
227
228
|
})
|
|
228
229
|
|
|
229
230
|
test('throws when no router installed', () => {
|
|
230
|
-
expect(() => useSearchParams()).toThrow('[
|
|
231
|
+
expect(() => useSearchParams()).toThrow('[Pyreon] No router installed')
|
|
231
232
|
})
|
|
232
233
|
|
|
233
234
|
test('returns query params from current route', () => {
|
|
@@ -513,3 +514,52 @@ describe('router — staleWhileRevalidate', () => {
|
|
|
513
514
|
expect(loaderCallCount).toBe(2)
|
|
514
515
|
})
|
|
515
516
|
})
|
|
517
|
+
|
|
518
|
+
describe('router.preload', () => {
|
|
519
|
+
test('runs loaders for the preloaded path', async () => {
|
|
520
|
+
let calls = 0
|
|
521
|
+
const routes: RouteRecord[] = [
|
|
522
|
+
{ path: '/', component: Home },
|
|
523
|
+
{
|
|
524
|
+
path: '/u/:id',
|
|
525
|
+
component: User,
|
|
526
|
+
loader: async ({ params }) => {
|
|
527
|
+
calls++
|
|
528
|
+
return { id: params.id, name: `User ${params.id}` }
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
]
|
|
532
|
+
const router = createRouter({ routes, url: '/' }) as RouterInstance
|
|
533
|
+
|
|
534
|
+
await router.preload('/u/7')
|
|
535
|
+
|
|
536
|
+
expect(calls).toBe(1)
|
|
537
|
+
expect(router._loaderData.get(routes[1] as RouteRecord)).toEqual({
|
|
538
|
+
id: '7',
|
|
539
|
+
name: 'User 7',
|
|
540
|
+
})
|
|
541
|
+
// currentRoute is unchanged — preload prepares data, doesn't navigate
|
|
542
|
+
expect(router.currentRoute().path).toBe('/')
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
test('loads lazy components into the cache so render is synchronous', async () => {
|
|
546
|
+
let lazyLoadCalls = 0
|
|
547
|
+
const Lazy = () => null
|
|
548
|
+
const routes: RouteRecord[] = [
|
|
549
|
+
{ path: '/', component: Home },
|
|
550
|
+
{
|
|
551
|
+
path: '/lazy',
|
|
552
|
+
component: lazy(async () => {
|
|
553
|
+
lazyLoadCalls++
|
|
554
|
+
return Lazy
|
|
555
|
+
}),
|
|
556
|
+
},
|
|
557
|
+
]
|
|
558
|
+
const router = createRouter({ routes, url: '/' }) as RouterInstance
|
|
559
|
+
|
|
560
|
+
await router.preload('/lazy')
|
|
561
|
+
|
|
562
|
+
expect(lazyLoadCalls).toBe(1)
|
|
563
|
+
expect(router._componentCache.get(routes[1] as RouteRecord)).toBe(Lazy)
|
|
564
|
+
})
|
|
565
|
+
})
|