@pyreon/router 0.12.13 → 0.12.15

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.
@@ -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
  }
@@ -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.13",
3
+ "version": "0.12.15",
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.13",
46
- "@pyreon/reactivity": "^0.12.13",
47
- "@pyreon/runtime-dom": "^0.12.13"
46
+ "@pyreon/core": "^0.12.15",
47
+ "@pyreon/reactivity": "^0.12.15",
48
+ "@pyreon/runtime-dom": "^0.12.15"
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/loader.ts CHANGED
@@ -35,8 +35,11 @@ export function useLoaderData<T = unknown>(): T {
35
35
  */
36
36
  export async function prefetchLoaderData(router: RouterInstance, path: string): Promise<void> {
37
37
  const route = router._resolve(path)
38
+ // Use a local AbortController — prefetch is best-effort and must NOT
39
+ // clobber `router._abortController`, which belongs to the active
40
+ // navigation. Previously, hovering a link during a navigation replaced
41
+ // the nav's controller, destroying its abort capability.
38
42
  const ac = new AbortController()
39
- router._abortController = ac
40
43
  await Promise.all(
41
44
  route.matched
42
45
  .filter((r) => r.loader)
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- '[pyreon-router] No router installed. Wrap your app in <RouterProvider router={router}>.',
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
- let _popstateHandler: (() => void) | null = null
433
- let _hashchangeHandler: (() => void) | null = null
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 (_isBrowser) {
436
- if (mode === 'history') {
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
- (document as any).startViewTransition(() => {
642
- doCommit()
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,49 @@ 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. Uses a LOCAL AbortController: `preload` is
928
+ // a prefetch operation and must NOT clobber `router._abortController`,
929
+ // which belongs to the active navigation. Without this, calling
930
+ // `router.preload(...)` during a navigation destroyed the nav's
931
+ // abort capability.
932
+ const ac = new AbortController()
933
+ await Promise.all(
934
+ resolved.matched
935
+ .filter((r) => r.loader)
936
+ .map(async (r) => {
937
+ const data = await r.loader?.({
938
+ params: resolved.params,
939
+ query: resolved.query,
940
+ signal: ac.signal,
941
+ })
942
+ router._loaderData.set(r, data)
943
+ }),
944
+ )
945
+ },
946
+
864
947
  destroy() {
865
- if (_popstateHandler) {
866
- window.removeEventListener('popstate', _popstateHandler)
867
- _popstateHandler = null
868
- }
869
- if (_hashchangeHandler) {
870
- window.removeEventListener('hashchange', _hashchangeHandler)
871
- _hashchangeHandler = null
872
- }
948
+ if (_popstateHandler) window.removeEventListener('popstate', _popstateHandler)
949
+ if (_hashchangeHandler) window.removeEventListener('hashchange', _hashchangeHandler)
873
950
  guards.length = 0
874
951
  afterHooks.length = 0
875
952
  router._blockers.clear()
package/src/scroll.ts CHANGED
@@ -6,6 +6,12 @@ import type { ResolvedRoute, RouterOptions } from './types'
6
6
  * Saves scroll position before each navigation and restores it when
7
7
  * navigating back to a previously visited path.
8
8
  */
9
+ // LRU cap — in SPAs with unbounded URL space (`/user/:id`, query-string
10
+ // variations, etc.) the `_positions` Map would grow per unique path
11
+ // forever. 100 entries covers typical back-navigation depth; beyond that,
12
+ // scroll restoration is a nice-to-have not a correctness requirement.
13
+ const MAX_SCROLL_POSITIONS = 100
14
+
9
15
  export class ScrollManager {
10
16
  private readonly _positions = new Map<string, number>()
11
17
  private readonly _behavior: RouterOptions['scrollBehavior']
@@ -16,8 +22,19 @@ export class ScrollManager {
16
22
 
17
23
  /** Call before navigating away — saves current scroll position for `fromPath` */
18
24
  save(fromPath: string): void {
19
- // save/restore are only called from browser navigation paths (guarded by caller)
25
+ // ScrollManager methods are only invoked from browser navigation paths,
26
+ // but an explicit early-return documents the SSR-safety contract at the
27
+ // callsite (the `no-window-in-ssr` lint rule can't AST-trace indirect
28
+ // calls from router setup).
29
+ if (typeof window === 'undefined') return
30
+ // LRU: re-insert moves the entry to newest. Evict oldest when over cap.
31
+ if (this._positions.has(fromPath)) this._positions.delete(fromPath)
20
32
  this._positions.set(fromPath, window.scrollY)
33
+ while (this._positions.size > MAX_SCROLL_POSITIONS) {
34
+ const oldest = this._positions.keys().next().value
35
+ if (oldest === undefined) break
36
+ this._positions.delete(oldest)
37
+ }
21
38
  }
22
39
 
23
40
  /** Call after navigation is committed — applies scroll behavior */
@@ -35,6 +52,7 @@ export class ScrollManager {
35
52
  }
36
53
 
37
54
  private _applyResult(result: 'top' | 'restore' | 'none' | number, toPath: string): void {
55
+ if (typeof window === 'undefined') return
38
56
  // Hash scrolling: if the path contains #, scroll to the element
39
57
  const hashIdx = toPath.indexOf('#')
40
58
  if (hashIdx >= 0) {
@@ -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
@@ -77,6 +78,24 @@ describe('loader data serialization — edge cases', () => {
77
78
  expect(values[0]).toEqual(complexData)
78
79
  })
79
80
 
81
+ test('prefetchLoaderData does NOT clobber router._abortController', async () => {
82
+ // Regression: `prefetchLoaderData` used to overwrite
83
+ // `router._abortController` with its own fresh controller. Hovering
84
+ // a <Link> during an in-flight navigation destroyed the nav's
85
+ // abort capability — subsequent navigations couldn't cancel the
86
+ // first one. Fix: prefetch uses a LOCAL controller.
87
+ const routes: RouteRecord[] = [
88
+ { path: '/data', component: Home, loader: async () => 'ok' },
89
+ ]
90
+ const router = createRouter({ routes, url: '/' }) as RouterInstance
91
+ const navController = new AbortController()
92
+ router._abortController = navController
93
+ await prefetchLoaderData(router, '/data')
94
+ // Prefetch finished; nav's controller must be untouched.
95
+ expect(router._abortController).toBe(navController)
96
+ expect(navController.signal.aborted).toBe(false)
97
+ })
98
+
80
99
  test('prefetchLoaderData passes AbortSignal to loaders', async () => {
81
100
  let receivedSignal: AbortSignal | undefined
82
101
  const routes: RouteRecord[] = [
@@ -119,7 +138,7 @@ describe('useIsActive — edge cases', () => {
119
138
  })
120
139
 
121
140
  test('throws when no router installed', () => {
122
- expect(() => useIsActive('/')).toThrow('[pyreon-router] No router installed')
141
+ expect(() => useIsActive('/')).toThrow('[Pyreon] No router installed')
123
142
  })
124
143
 
125
144
  test('exact match for root path', () => {
@@ -227,7 +246,7 @@ describe('useSearchParams — edge cases', () => {
227
246
  })
228
247
 
229
248
  test('throws when no router installed', () => {
230
- expect(() => useSearchParams()).toThrow('[pyreon-router] No router installed')
249
+ expect(() => useSearchParams()).toThrow('[Pyreon] No router installed')
231
250
  })
232
251
 
233
252
  test('returns query params from current route', () => {
@@ -513,3 +532,52 @@ describe('router — staleWhileRevalidate', () => {
513
532
  expect(loaderCallCount).toBe(2)
514
533
  })
515
534
  })
535
+
536
+ describe('router.preload', () => {
537
+ test('runs loaders for the preloaded path', async () => {
538
+ let calls = 0
539
+ const routes: RouteRecord[] = [
540
+ { path: '/', component: Home },
541
+ {
542
+ path: '/u/:id',
543
+ component: User,
544
+ loader: async ({ params }) => {
545
+ calls++
546
+ return { id: params.id, name: `User ${params.id}` }
547
+ },
548
+ },
549
+ ]
550
+ const router = createRouter({ routes, url: '/' }) as RouterInstance
551
+
552
+ await router.preload('/u/7')
553
+
554
+ expect(calls).toBe(1)
555
+ expect(router._loaderData.get(routes[1] as RouteRecord)).toEqual({
556
+ id: '7',
557
+ name: 'User 7',
558
+ })
559
+ // currentRoute is unchanged — preload prepares data, doesn't navigate
560
+ expect(router.currentRoute().path).toBe('/')
561
+ })
562
+
563
+ test('loads lazy components into the cache so render is synchronous', async () => {
564
+ let lazyLoadCalls = 0
565
+ const Lazy = () => null
566
+ const routes: RouteRecord[] = [
567
+ { path: '/', component: Home },
568
+ {
569
+ path: '/lazy',
570
+ component: lazy(async () => {
571
+ lazyLoadCalls++
572
+ return Lazy
573
+ }),
574
+ },
575
+ ]
576
+ const router = createRouter({ routes, url: '/' }) as RouterInstance
577
+
578
+ await router.preload('/lazy')
579
+
580
+ expect(lazyLoadCalls).toBe(1)
581
+ expect(router._componentCache.get(routes[1] as RouteRecord)).toBe(Lazy)
582
+ })
583
+ })