@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1b930379

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.
Files changed (84) hide show
  1. package/README.md +46 -12
  2. package/dist/bin/rango.js +109 -15
  3. package/dist/vite/index.js +323 -121
  4. package/package.json +15 -16
  5. package/skills/breadcrumbs/SKILL.md +250 -0
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +33 -31
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/loader/SKILL.md +55 -15
  11. package/skills/prerender/SKILL.md +2 -2
  12. package/skills/rango/SKILL.md +0 -1
  13. package/skills/route/SKILL.md +3 -4
  14. package/skills/router-setup/SKILL.md +8 -3
  15. package/skills/typesafety/SKILL.md +25 -23
  16. package/src/__internal.ts +92 -0
  17. package/src/bin/rango.ts +18 -0
  18. package/src/browser/link-interceptor.ts +4 -0
  19. package/src/browser/navigation-bridge.ts +95 -5
  20. package/src/browser/navigation-client.ts +97 -72
  21. package/src/browser/prefetch/cache.ts +112 -25
  22. package/src/browser/prefetch/fetch.ts +28 -30
  23. package/src/browser/prefetch/policy.ts +6 -0
  24. package/src/browser/react/Link.tsx +19 -7
  25. package/src/browser/rsc-router.tsx +11 -2
  26. package/src/browser/server-action-bridge.ts +448 -432
  27. package/src/browser/types.ts +24 -0
  28. package/src/build/generate-route-types.ts +2 -0
  29. package/src/build/route-trie.ts +19 -3
  30. package/src/build/route-types/router-processing.ts +125 -15
  31. package/src/client.rsc.tsx +2 -1
  32. package/src/client.tsx +1 -46
  33. package/src/handles/breadcrumbs.ts +66 -0
  34. package/src/handles/index.ts +1 -0
  35. package/src/host/index.ts +0 -3
  36. package/src/index.rsc.ts +5 -36
  37. package/src/index.ts +32 -66
  38. package/src/prerender/store.ts +56 -15
  39. package/src/route-definition/index.ts +0 -3
  40. package/src/router/handler-context.ts +30 -3
  41. package/src/router/loader-resolution.ts +1 -1
  42. package/src/router/match-api.ts +1 -1
  43. package/src/router/match-result.ts +0 -9
  44. package/src/router/metrics.ts +233 -13
  45. package/src/router/middleware-types.ts +53 -10
  46. package/src/router/middleware.ts +170 -81
  47. package/src/router/pattern-matching.ts +20 -5
  48. package/src/router/prerender-match.ts +4 -0
  49. package/src/router/revalidation.ts +27 -7
  50. package/src/router/router-interfaces.ts +14 -1
  51. package/src/router/router-options.ts +13 -8
  52. package/src/router/segment-resolution/fresh.ts +18 -0
  53. package/src/router/segment-resolution/helpers.ts +1 -1
  54. package/src/router/segment-resolution/revalidation.ts +22 -9
  55. package/src/router/trie-matching.ts +20 -2
  56. package/src/router.ts +29 -9
  57. package/src/rsc/handler.ts +106 -11
  58. package/src/rsc/index.ts +0 -20
  59. package/src/rsc/progressive-enhancement.ts +21 -8
  60. package/src/rsc/rsc-rendering.ts +30 -43
  61. package/src/rsc/server-action.ts +14 -10
  62. package/src/rsc/ssr-setup.ts +128 -0
  63. package/src/rsc/types.ts +2 -0
  64. package/src/search-params.ts +16 -13
  65. package/src/server/context.ts +8 -2
  66. package/src/server/request-context.ts +38 -16
  67. package/src/server.ts +6 -0
  68. package/src/theme/index.ts +4 -13
  69. package/src/types/handler-context.ts +12 -16
  70. package/src/types/route-config.ts +17 -8
  71. package/src/types/segments.ts +0 -5
  72. package/src/vite/discovery/bundle-postprocess.ts +31 -56
  73. package/src/vite/discovery/discover-routers.ts +18 -4
  74. package/src/vite/discovery/prerender-collection.ts +34 -14
  75. package/src/vite/discovery/state.ts +4 -7
  76. package/src/vite/index.ts +4 -3
  77. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  78. package/src/vite/plugins/refresh-cmd.ts +65 -0
  79. package/src/vite/rango.ts +11 -0
  80. package/src/vite/router-discovery.ts +16 -0
  81. package/src/vite/utils/prerender-utils.ts +60 -0
  82. package/skills/testing/SKILL.md +0 -226
  83. package/src/route-definition/route-function.ts +0 -119
  84. /package/{CLAUDE.md → AGENTS.md} +0 -0
@@ -5,6 +5,8 @@
5
5
  * Honors browser reduced-data preferences when available.
6
6
  */
7
7
 
8
+ import { isPrefetchCacheDisabled } from "./cache.js";
9
+
8
10
  type NavigatorWithConnection = Navigator & {
9
11
  connection?: {
10
12
  saveData?: boolean;
@@ -18,6 +20,10 @@ type NavigatorWithConnection = Navigator & {
18
20
  export function shouldPrefetch(): boolean {
19
21
  if (typeof window === "undefined") return false;
20
22
 
23
+ // When prefetchCacheTTL is false/0, prefetching is fully disabled —
24
+ // no point issuing requests whose responses will be discarded.
25
+ if (isPrefetchCacheDisabled()) return false;
26
+
21
27
  const nav =
22
28
  typeof navigator !== "undefined"
23
29
  ? (navigator as NavigatorWithConnection)
@@ -37,7 +37,7 @@ import {
37
37
  unobserveForPrefetch,
38
38
  } from "../prefetch/observer.js";
39
39
 
40
- // Touch device detection for hybrid strategy.
40
+ // Touch device detection for adaptive strategy.
41
41
  // Checked once at module load (Link.tsx is "use client", runs only in browser).
42
42
  const isTouchDevice =
43
43
  typeof window !== "undefined" && window.matchMedia("(hover: none)").matches;
@@ -47,14 +47,14 @@ const isTouchDevice =
47
47
  * - "hover": Prefetch on mouse enter (direct, no queue)
48
48
  * - "viewport": Prefetch when link enters viewport (queued, waits for idle)
49
49
  * - "render": Prefetch on component mount regardless of visibility (queued, waits for idle)
50
- * - "hybrid": Hover on pointer devices, viewport on touch devices
50
+ * - "adaptive": Hover on pointer devices, viewport on touch devices
51
51
  * - "none": No prefetching (default)
52
52
  */
53
53
  export type PrefetchStrategy =
54
54
  | "hover"
55
55
  | "viewport"
56
56
  | "render"
57
- | "hybrid"
57
+ | "adaptive"
58
58
  | "none";
59
59
 
60
60
  /**
@@ -80,6 +80,16 @@ export interface LinkProps extends Omit<
80
80
  * Force full document navigation instead of SPA
81
81
  */
82
82
  reloadDocument?: boolean;
83
+ /**
84
+ * Whether to revalidate server data on navigation.
85
+ * Set to `false` to skip the RSC server fetch and only update the URL.
86
+ *
87
+ * Only takes effect when the pathname stays the same (search param / hash changes).
88
+ * If the pathname changes, this option is ignored and a full navigation occurs.
89
+ *
90
+ * @default true
91
+ */
92
+ revalidate?: boolean;
83
93
  /**
84
94
  * Prefetch strategy for the link destination
85
95
  * @default "none"
@@ -170,6 +180,7 @@ export const Link: ForwardRefExoticComponent<
170
180
  replace = false,
171
181
  scroll = true,
172
182
  reloadDocument = false,
183
+ revalidate,
173
184
  prefetch = "none",
174
185
  state,
175
186
  children,
@@ -181,9 +192,9 @@ export const Link: ForwardRefExoticComponent<
181
192
  const ctx = useContext(NavigationStoreContext);
182
193
  const isExternal = isExternalUrl(to);
183
194
 
184
- // Resolve hybrid: viewport on touch devices, hover on pointer devices
195
+ // Resolve adaptive: viewport on touch devices, hover on pointer devices
185
196
  const resolvedStrategy =
186
- prefetch === "hybrid" ? (isTouchDevice ? "viewport" : "hover") : prefetch;
197
+ prefetch === "adaptive" ? (isTouchDevice ? "viewport" : "hover") : prefetch;
187
198
 
188
199
  // Internal ref for viewport observation; merge with forwarded ref
189
200
  const internalRef = useRef<HTMLAnchorElement | null>(null);
@@ -262,9 +273,9 @@ export const Link: ForwardRefExoticComponent<
262
273
  resolvedState = currentState;
263
274
  }
264
275
 
265
- ctx.navigate(to, { replace, scroll, state: resolvedState });
276
+ ctx.navigate(to, { replace, scroll, state: resolvedState, revalidate });
266
277
  },
267
- [to, isExternal, reloadDocument, replace, scroll, ctx, onClick],
278
+ [to, isExternal, reloadDocument, replace, scroll, revalidate, ctx, onClick],
268
279
  );
269
280
 
270
281
  const handleMouseEnter = useCallback(() => {
@@ -340,6 +351,7 @@ export const Link: ForwardRefExoticComponent<
340
351
  data-external={isExternal ? "" : undefined}
341
352
  data-scroll={scroll === false ? "false" : undefined}
342
353
  data-replace={replace ? "true" : undefined}
354
+ data-revalidate={revalidate === false ? "false" : undefined}
343
355
  {...props}
344
356
  >
345
357
  <LinkContext.Provider value={to}>{children}</LinkContext.Provider>
@@ -22,6 +22,7 @@ import type {
22
22
  import type { EventController } from "./event-controller.js";
23
23
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
24
24
  import { initRangoState } from "./rango-state.js";
25
+ import { initPrefetchCache } from "./prefetch/cache.js";
25
26
  import {
26
27
  isInterceptSegment,
27
28
  splitInterceptSegments,
@@ -201,10 +202,17 @@ export async function initBrowserApp(
201
202
  const rootLayout = initialPayload.metadata?.rootLayout;
202
203
  const version = initialPayload.metadata?.version;
203
204
 
204
- // Initialize the localStorage state key for browser HTTP cache invalidation.
205
+ // Initialize the localStorage state key for cache invalidation.
205
206
  // Uses the build version so a new deploy automatically busts all cached prefetches.
206
207
  initRangoState(version ?? "0");
207
208
 
209
+ // Initialize the in-memory prefetch cache TTL from server config.
210
+ // A value of 0 disables the cache; undefined falls back to the module default.
211
+ const prefetchCacheTTL = initialPayload.metadata?.prefetchCacheTTL;
212
+ if (prefetchCacheTTL !== undefined) {
213
+ initPrefetchCache(prefetchCacheTTL);
214
+ }
215
+
208
216
  // Create a bound renderSegments that includes rootLayout
209
217
  const renderSegments = (
210
218
  segments: ResolvedSegment[],
@@ -260,7 +268,7 @@ export async function initBrowserApp(
260
268
  import.meta.hot.on("rsc:update", async () => {
261
269
  console.log("[RSCRouter] HMR: Server update, refetching RSC");
262
270
 
263
- using handle = eventController.startNavigation(window.location.href, {
271
+ const handle = eventController.startNavigation(window.location.href, {
264
272
  replace: true,
265
273
  });
266
274
  const streamingToken = handle.startStreaming();
@@ -318,6 +326,7 @@ export async function initBrowserApp(
318
326
  console.log("[RSCRouter] HMR: RSC stream complete");
319
327
  } finally {
320
328
  streamingToken.end();
329
+ handle[Symbol.dispose]();
321
330
  }
322
331
  });
323
332
  }