@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430

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 (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4474 -867
  5. package/package.json +60 -51
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +87 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +285 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +258 -308
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +185 -73
  54. package/src/browser/react/NavigationProvider.tsx +51 -11
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +6 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +107 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +109 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +3 -1
  114. package/src/client.tsx +106 -126
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +21 -15
  126. package/src/host/errors.ts +8 -8
  127. package/src/host/index.ts +4 -7
  128. package/src/host/pattern-matcher.ts +27 -27
  129. package/src/host/router.ts +61 -39
  130. package/src/host/testing.ts +8 -8
  131. package/src/host/types.ts +15 -7
  132. package/src/host/utils.ts +1 -1
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -157
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +934 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +211 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +158 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +395 -0
  162. package/src/router/lazy-includes.ts +234 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +248 -0
  165. package/src/router/manifest.ts +148 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +80 -93
  170. package/src/router/match-middleware/cache-lookup.ts +382 -9
  171. package/src/router/match-middleware/cache-store.ts +51 -22
  172. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  173. package/src/router/match-middleware/segment-resolution.ts +24 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +34 -28
  176. package/src/router/metrics.ts +235 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +324 -367
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +36 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +570 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +198 -0
  191. package/src/router/segment-resolution/revalidation.ts +1241 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +289 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +692 -4257
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +235 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +25 -13
  219. package/src/server/context.ts +182 -51
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +430 -70
  225. package/src/server.ts +35 -130
  226. package/src/ssr/index.tsx +100 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +687 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +102 -0
  243. package/src/types/segments.ts +148 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +110 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -1133
  263. package/src/vite/plugin-types.ts +131 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +254 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +510 -0
  282. package/src/vite/router-discovery.ts +785 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/package.json CHANGED
@@ -1,34 +1,44 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.8",
4
- "type": "module",
3
+ "version": "0.0.0-experimental.8a4d0430",
5
4
  "description": "Django-inspired RSC router with composable URL patterns",
6
- "author": "Ivo Todorov",
5
+ "keywords": [
6
+ "react",
7
+ "react-server-components",
8
+ "router",
9
+ "rsc",
10
+ "vite"
11
+ ],
12
+ "homepage": "https://github.com/ivogt/vite-rsc#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ivogt/vite-rsc/issues"
15
+ },
7
16
  "license": "MIT",
17
+ "author": "Ivo Todorov",
8
18
  "repository": {
9
19
  "type": "git",
10
20
  "url": "git+https://github.com/ivogt/vite-rsc.git",
11
21
  "directory": "packages/rangojs-router"
12
22
  },
13
- "homepage": "https://github.com/ivogt/vite-rsc#readme",
14
- "bugs": {
15
- "url": "https://github.com/ivogt/vite-rsc/issues"
23
+ "bin": {
24
+ "rango": "./dist/bin/rango.js"
16
25
  },
17
- "publishConfig": {
18
- "access": "public",
19
- "tag": "experimental"
20
- },
21
- "keywords": [
22
- "react",
23
- "rsc",
24
- "react-server-components",
25
- "router",
26
- "vite"
26
+ "files": [
27
+ "src",
28
+ "!src/**/__tests__",
29
+ "!src/**/__mocks__",
30
+ "!src/**/*.test.ts",
31
+ "!src/**/*.test.tsx",
32
+ "dist",
33
+ "skills",
34
+ "AGENTS.md",
35
+ "README.md"
27
36
  ],
37
+ "type": "module",
28
38
  "exports": {
29
39
  ".": {
30
- "react-server": "./src/index.rsc.ts",
31
40
  "types": "./src/index.rsc.ts",
41
+ "react-server": "./src/index.rsc.ts",
32
42
  "default": "./src/index.ts"
33
43
  },
34
44
  "./server": {
@@ -36,8 +46,8 @@
36
46
  "import": "./src/server.ts"
37
47
  },
38
48
  "./client": {
39
- "react-server": "./src/client.rsc.tsx",
40
49
  "types": "./src/client.tsx",
50
+ "react-server": "./src/client.rsc.tsx",
41
51
  "default": "./src/client.tsx"
42
52
  },
43
53
  "./browser": {
@@ -49,8 +59,8 @@
49
59
  "default": "./src/ssr/index.tsx"
50
60
  },
51
61
  "./rsc": {
52
- "react-server": "./src/rsc/index.ts",
53
62
  "types": "./src/rsc/index.ts",
63
+ "react-server": "./src/rsc/index.ts",
54
64
  "default": "./src/rsc/index.ts"
55
65
  },
56
66
  "./vite": {
@@ -58,7 +68,7 @@
58
68
  "import": "./dist/vite/index.js"
59
69
  },
60
70
  "./types": {
61
- "types": "./src/vite/version.d.ts"
71
+ "types": "./src/vite/plugins/version.d.ts"
62
72
  },
63
73
  "./__internal": {
64
74
  "types": "./src/__internal.ts",
@@ -73,8 +83,8 @@
73
83
  "default": "./src/deps/ssr.ts"
74
84
  },
75
85
  "./internal/deps/rsc": {
76
- "react-server": "./src/deps/rsc.ts",
77
86
  "types": "./src/deps/rsc.ts",
87
+ "react-server": "./src/deps/rsc.ts",
78
88
  "default": "./src/deps/rsc.ts"
79
89
  },
80
90
  "./internal/deps/html-stream-client": {
@@ -86,15 +96,20 @@
86
96
  "default": "./src/deps/html-stream-server.ts"
87
97
  },
88
98
  "./internal/rsc-handler": {
89
- "react-server": "./src/rsc/handler.ts",
90
99
  "types": "./src/rsc/handler.ts",
100
+ "react-server": "./src/rsc/handler.ts",
91
101
  "default": "./src/rsc/handler.ts"
92
102
  },
93
103
  "./cache": {
94
- "react-server": "./src/cache/index.ts",
95
104
  "types": "./src/cache/index.ts",
105
+ "react-server": "./src/cache/index.ts",
96
106
  "default": "./src/cache/index.ts"
97
107
  },
108
+ "./cache-runtime": {
109
+ "types": "./src/cache/cache-runtime.ts",
110
+ "react-server": "./src/cache/cache-runtime.ts",
111
+ "default": "./src/cache/cache-runtime.ts"
112
+ },
98
113
  "./theme": {
99
114
  "types": "./src/theme/index.ts",
100
115
  "default": "./src/theme/index.ts"
@@ -104,8 +119,8 @@
104
119
  "import": "./src/build/index.ts"
105
120
  },
106
121
  "./host": {
107
- "react-server": "./src/host/index.ts",
108
122
  "types": "./src/host/index.ts",
123
+ "react-server": "./src/host/index.ts",
109
124
  "default": "./src/host/index.ts"
110
125
  },
111
126
  "./host/testing": {
@@ -113,34 +128,14 @@
113
128
  "default": "./src/host/testing.ts"
114
129
  }
115
130
  },
116
- "files": [
117
- "src",
118
- "!src/**/__tests__",
119
- "!src/**/__mocks__",
120
- "!src/**/*.test.ts",
121
- "!src/**/*.test.tsx",
122
- "dist",
123
- "skills",
124
- "CLAUDE.md",
125
- "README.md"
126
- ],
127
- "peerDependencies": {
128
- "@cloudflare/vite-plugin": "^1.21.0",
129
- "@vitejs/plugin-rsc": "^0.5.14",
130
- "react": "^18.0.0 || ^19.0.0",
131
- "vite": "^7.3.0"
132
- },
133
- "peerDependenciesMeta": {
134
- "@cloudflare/vite-plugin": {
135
- "optional": true
136
- },
137
- "vite": {
138
- "optional": true
139
- }
131
+ "publishConfig": {
132
+ "access": "public",
133
+ "tag": "experimental"
140
134
  },
141
135
  "dependencies": {
142
136
  "@vitejs/plugin-rsc": "^0.5.14",
143
137
  "magic-string": "^0.30.17",
138
+ "picomatch": "^4.0.3",
144
139
  "rsc-html-stream": "^0.0.7"
145
140
  },
146
141
  "devDependencies": {
@@ -150,14 +145,28 @@
150
145
  "@types/react-dom": "^19.2.3",
151
146
  "esbuild": "^0.27.0",
152
147
  "jiti": "^2.6.1",
153
- "react": "^19.2.1",
154
- "react-dom": "^19.2.1",
148
+ "react": "^19.2.4",
149
+ "react-dom": "^19.2.4",
155
150
  "tinyexec": "^0.3.2",
156
151
  "typescript": "^5.3.0",
157
152
  "vitest": "^4.0.0"
158
153
  },
154
+ "peerDependencies": {
155
+ "@cloudflare/vite-plugin": "^1.25.0",
156
+ "@vitejs/plugin-rsc": "^0.5.14",
157
+ "react": "^18.0.0 || ^19.0.0",
158
+ "vite": "^7.3.0"
159
+ },
160
+ "peerDependenciesMeta": {
161
+ "@cloudflare/vite-plugin": {
162
+ "optional": true
163
+ },
164
+ "vite": {
165
+ "optional": true
166
+ }
167
+ },
159
168
  "scripts": {
160
- "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external",
169
+ "build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
161
170
  "typecheck": "tsc --noEmit",
162
171
  "test": "playwright test",
163
172
  "test:ui": "playwright test --ui",
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: breadcrumbs
3
+ description: Built-in Breadcrumbs handle for accumulating breadcrumb navigation across route segments
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Breadcrumbs
8
+
9
+ Built-in handle for accumulating breadcrumb items across route segments.
10
+ Each layout/route pushes items via `ctx.use(Breadcrumbs)`, and they are
11
+ collected in parent-to-child order with automatic deduplication by `href`.
12
+
13
+ ## BreadcrumbItem Type
14
+
15
+ ```typescript
16
+ interface BreadcrumbItem {
17
+ label: string; // Display text
18
+ href: string; // URL the breadcrumb links to
19
+ content?: ReactNode | Promise<ReactNode>; // Optional extra content (sync or async)
20
+ }
21
+ ```
22
+
23
+ ## Pushing Breadcrumbs (Server)
24
+
25
+ Import `Breadcrumbs` from `@rangojs/router` in RSC/server context:
26
+
27
+ ```typescript
28
+ import { urls, Breadcrumbs } from "@rangojs/router";
29
+ import { Outlet } from "@rangojs/router/client";
30
+
31
+ export const urlpatterns = urls(({ path, layout }) => [
32
+ // Root layout pushes "Home"
33
+ layout((ctx) => {
34
+ const breadcrumb = ctx.use(Breadcrumbs);
35
+ breadcrumb({ label: "Home", href: "/" });
36
+ return <RootLayout />;
37
+ }, () => [
38
+ path("/", HomePage, { name: "home" }),
39
+
40
+ // Nested layout pushes "Blog"
41
+ layout((ctx) => {
42
+ const breadcrumb = ctx.use(Breadcrumbs);
43
+ breadcrumb({ label: "Blog", href: "/blog" });
44
+ return <BlogLayout />;
45
+ }, () => [
46
+ path("/blog", BlogIndex, { name: "blog.index" }),
47
+
48
+ // Route handler pushes post title
49
+ path("/blog/:slug", (ctx) => {
50
+ const breadcrumb = ctx.use(Breadcrumbs);
51
+ breadcrumb({ label: ctx.params.slug, href: `/blog/${ctx.params.slug}` });
52
+ return <BlogPost slug={ctx.params.slug} />;
53
+ }, { name: "blog.post" }),
54
+ ]),
55
+ ]),
56
+ ]);
57
+ ```
58
+
59
+ On `/blog/my-post`, breadcrumbs accumulate: `Home > Blog > my-post`.
60
+
61
+ ## Async Content
62
+
63
+ The `content` field supports `Promise<ReactNode>` for streaming:
64
+
65
+ ```typescript
66
+ path("/product/:id", async (ctx) => {
67
+ const breadcrumb = ctx.use(Breadcrumbs);
68
+ const productPromise = fetchProduct(ctx.params.id);
69
+
70
+ breadcrumb({
71
+ label: "Product",
72
+ href: `/product/${ctx.params.id}`,
73
+ content: productPromise.then((p) => <span>({p.category})</span>),
74
+ });
75
+
76
+ const product = await productPromise;
77
+ return <ProductPage product={product} />;
78
+ }, { name: "product" })
79
+ ```
80
+
81
+ Async content is a `Promise<ReactNode>`. Resolve it in your component
82
+ with React's `use()` hook wrapped in `<Suspense>`.
83
+
84
+ ## Consuming Breadcrumbs (Client)
85
+
86
+ Use `useHandle(Breadcrumbs)` in a client component to read the accumulated items:
87
+
88
+ ```tsx
89
+ "use client";
90
+ import { useHandle, Breadcrumbs, Link } from "@rangojs/router/client";
91
+
92
+ function BreadcrumbNav() {
93
+ const breadcrumbs = useHandle(Breadcrumbs);
94
+
95
+ if (!breadcrumbs.length) return null;
96
+
97
+ return (
98
+ <nav aria-label="Breadcrumb">
99
+ <ol>
100
+ {breadcrumbs.map((crumb, i) => (
101
+ <li key={crumb.href}>
102
+ {i === breadcrumbs.length - 1 ? (
103
+ <span aria-current="page">{crumb.label}</span>
104
+ ) : (
105
+ <Link to={crumb.href}>{crumb.label}</Link>
106
+ )}
107
+ </li>
108
+ ))}
109
+ </ol>
110
+ </nav>
111
+ );
112
+ }
113
+ ```
114
+
115
+ ### With Selector
116
+
117
+ Re-render only when the selected value changes:
118
+
119
+ ```tsx
120
+ // Only the last breadcrumb
121
+ const current = useHandle(Breadcrumbs, (data) => data.at(-1));
122
+
123
+ // Breadcrumb count
124
+ const count = useHandle(Breadcrumbs, (data) => data.length);
125
+ ```
126
+
127
+ ## Deduplication
128
+
129
+ The built-in collect function deduplicates by `href`. If multiple segments
130
+ push the same `href`, the last one wins. This prevents duplicates when
131
+ navigating between sibling routes that share a common breadcrumb.
132
+
133
+ ## Passing as Props
134
+
135
+ Breadcrumbs handle can be passed from server to client components:
136
+
137
+ ```tsx
138
+ // Server component
139
+ path("/dashboard", (ctx) => {
140
+ const breadcrumb = ctx.use(Breadcrumbs);
141
+ breadcrumb({ label: "Dashboard", href: "/dashboard" });
142
+ return <DashboardNav handle={Breadcrumbs} />;
143
+ });
144
+
145
+ // Client component
146
+ ("use client");
147
+ import { useHandle, type Breadcrumbs } from "@rangojs/router/client";
148
+
149
+ function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
150
+ const crumbs = useHandle(handle);
151
+ return (
152
+ <nav>
153
+ {crumbs.map((c) => (
154
+ <a href={c.href}>{c.label}</a>
155
+ ))}
156
+ </nav>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ## Complete Example
162
+
163
+ ```typescript
164
+ // urls.tsx
165
+ import { urls, Breadcrumbs, Meta } from "@rangojs/router";
166
+ import { Outlet, MetaTags } from "@rangojs/router/client";
167
+ import { BreadcrumbNav } from "./components/BreadcrumbNav";
168
+
169
+ function RootLayout() {
170
+ return (
171
+ <html lang="en">
172
+ <head><MetaTags /></head>
173
+ <body>
174
+ <BreadcrumbNav />
175
+ <main><Outlet /></main>
176
+ </body>
177
+ </html>
178
+ );
179
+ }
180
+
181
+ export const urlpatterns = urls(({ path, layout }) => [
182
+ layout((ctx) => {
183
+ ctx.use(Breadcrumbs)({ label: "Home", href: "/" });
184
+ ctx.use(Meta)({ title: "My App" });
185
+ return <RootLayout />;
186
+ }, () => [
187
+ path("/", () => <h1>Welcome</h1>, { name: "home" }),
188
+
189
+ layout((ctx) => {
190
+ ctx.use(Breadcrumbs)({ label: "Shop", href: "/shop" });
191
+ return <Outlet />;
192
+ }, () => [
193
+ path("/shop", () => <h1>Shop</h1>, { name: "shop" }),
194
+ path("/shop/:slug", (ctx) => {
195
+ ctx.use(Breadcrumbs)({
196
+ label: ctx.params.slug,
197
+ href: `/shop/${ctx.params.slug}`,
198
+ });
199
+ return <h1>Product: {ctx.params.slug}</h1>;
200
+ }, { name: "shop.product" }),
201
+ ]),
202
+ ]),
203
+ ]);
204
+ ```
205
+
206
+ Navigating to `/shop/widget` produces: `Home / Shop / widget`
207
+
208
+ ## Custom Handles
209
+
210
+ Create your own handle with `createHandle()`:
211
+
212
+ ```typescript
213
+ import { createHandle } from "@rangojs/router";
214
+
215
+ // Default: flatten into array
216
+ export const PageTitle = createHandle<string, string>(
217
+ (segments) => segments.flat().at(-1) ?? "Default Title",
218
+ );
219
+
220
+ // No collect function: default flattens into T[]
221
+ export const Warnings = createHandle<string>();
222
+ ```
223
+
224
+ The Vite `exposeInternalIds` plugin auto-injects a stable `$$id` based on
225
+ file path and export name. No manual naming required for project-local code.
226
+
227
+ ### Handles in 3rd-party packages
228
+
229
+ The `exposeInternalIds` plugin skips `node_modules/`, so handles defined in
230
+ published packages won't get auto-injected IDs. Pass a manual tag as the
231
+ second argument to `createHandle()`:
232
+
233
+ ```typescript
234
+ import { createHandle } from "@rangojs/router";
235
+
236
+ // With a collect function (reducer): collect is first arg, tag is second
237
+ export const Breadcrumbs = createHandle<BreadcrumbItem, BreadcrumbItem[]>(
238
+ collectBreadcrumbs,
239
+ "__my_package_breadcrumbs__",
240
+ );
241
+
242
+ // Without a collect function: pass undefined, then the tag
243
+ export const Warnings = createHandle<string>(
244
+ undefined,
245
+ "__my_package_warnings__",
246
+ );
247
+ ```
248
+
249
+ The tag must be globally unique and stable across builds. Without it,
250
+ `createHandle` throws in development mode.