@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
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/AGENTS.md +5 -0
- package/README.md +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1577
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -726
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-cache
|
|
3
|
+
description: Function-level caching with "use cache" directive for RSC data functions and components
|
|
4
|
+
argument-hint: [profile-name]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# "use cache" Directive
|
|
8
|
+
|
|
9
|
+
Function-level caching for async server functions and RSC components. Caches
|
|
10
|
+
return values with TTL + stale-while-revalidate. Complementary to the route-level
|
|
11
|
+
`cache()` DSL and build-time `Static()`/`Prerender()`.
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### File-level (all exports cached with default profile)
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
"use cache";
|
|
19
|
+
|
|
20
|
+
export async function getProducts() {
|
|
21
|
+
return await db.query("SELECT * FROM products");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getCategories() {
|
|
25
|
+
return await db.query("SELECT * FROM categories");
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Function-level (per-function profile)
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export async function getProducts() {
|
|
33
|
+
"use cache: short";
|
|
34
|
+
return await db.query("SELECT * FROM products");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getCategories() {
|
|
38
|
+
"use cache: long";
|
|
39
|
+
return await db.query("SELECT * FROM categories");
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### RSC component
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
export async function ProductCard({ id }: { id: string }) {
|
|
47
|
+
"use cache: products"
|
|
48
|
+
const product = await db.query('SELECT * FROM products WHERE id = ?', [id]);
|
|
49
|
+
return <div>{product.name}</div>;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Named Cache Profiles
|
|
54
|
+
|
|
55
|
+
Define profiles in createRouter. Profile names map to `"use cache: <name>"` and
|
|
56
|
+
`cache('<name>')` in the DSL.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
createRouter({
|
|
60
|
+
cacheProfiles: {
|
|
61
|
+
default: { ttl: 900, swr: 1800 },
|
|
62
|
+
short: { ttl: 60, swr: 120 },
|
|
63
|
+
long: { ttl: 3600, swr: 7200 },
|
|
64
|
+
products: { ttl: 300, swr: 600, tags: ["products"] },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `"use cache"` (no name) resolves to `default`.
|
|
70
|
+
- `"use cache: short"` resolves to the `short` profile.
|
|
71
|
+
- Unknown profile names throw at build/boot time.
|
|
72
|
+
|
|
73
|
+
## Cache Key
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
use-cache:{functionId}:{serializedArgs}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `functionId` -- stable ID from Vite transform (module path + export name).
|
|
80
|
+
- `serializedArgs` -- non-tainted arguments serialized via RSC `encodeReply()`.
|
|
81
|
+
|
|
82
|
+
Different functions always produce different cache keys, even for the same route.
|
|
83
|
+
This is important for intercepted routes -- the path handler and intercept handler
|
|
84
|
+
each have their own `functionId` and therefore their own cache entries.
|
|
85
|
+
|
|
86
|
+
## Tainted Arguments (ctx, env, req)
|
|
87
|
+
|
|
88
|
+
Request-scoped objects are branded with `Symbol.for('rango:nocache')` at creation.
|
|
89
|
+
When detected:
|
|
90
|
+
|
|
91
|
+
1. **Excluded from cache key** -- request-scoped, not meaningful for keying.
|
|
92
|
+
2. **Handle data captured on miss** -- side effects via `ctx.use(Handle)` are recorded.
|
|
93
|
+
3. **Handle data replayed on hit** -- restored into the current request's HandleStore.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export async function getProductData(ctx) {
|
|
97
|
+
"use cache: short";
|
|
98
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
99
|
+
breadcrumb({ label: "Products", href: "/products" });
|
|
100
|
+
return await db.query("SELECT * FROM products");
|
|
101
|
+
}
|
|
102
|
+
// On hit: return value restored, breadcrumb replayed.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Request-Scoped Guards
|
|
106
|
+
|
|
107
|
+
### Read Guards
|
|
108
|
+
|
|
109
|
+
`cookies()` and `headers()` **throw** inside a `"use cache"` function because
|
|
110
|
+
per-request values (cookies, headers) are not reflected in the cache key. Without
|
|
111
|
+
this guard, one user's data would be served to another.
|
|
112
|
+
|
|
113
|
+
Extract the value before the cached function and pass it as an argument:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const locale = cookies().get("locale")?.value ?? "en";
|
|
117
|
+
const data = await getCachedData(locale); // locale is now in the cache key
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Side-Effect Guards
|
|
121
|
+
|
|
122
|
+
These ctx methods **throw** inside a `"use cache"` function because their effects
|
|
123
|
+
are lost on cache hit (the function body is skipped):
|
|
124
|
+
|
|
125
|
+
- `ctx.set()` / `ctx.get()` for passing values to children
|
|
126
|
+
- `ctx.header()`
|
|
127
|
+
- `ctx.setTheme()`
|
|
128
|
+
- `ctx.setLocationState()`
|
|
129
|
+
- `ctx.onResponse()`
|
|
130
|
+
|
|
131
|
+
The error message recommends two alternatives:
|
|
132
|
+
|
|
133
|
+
1. Extract the data fetch into a separate cached function and call ctx methods outside it.
|
|
134
|
+
2. Use the route-level `cache()` DSL which caches all segments together.
|
|
135
|
+
|
|
136
|
+
**`ctx.use(Handle)` is NOT guarded** -- handle push is captured on miss and replayed
|
|
137
|
+
on hit. This is the correct way to pass data from cached functions.
|
|
138
|
+
|
|
139
|
+
### Pattern: Separate cached function from ctx side effects
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Cached data fetch (pure)
|
|
143
|
+
async function getNavData() {
|
|
144
|
+
"use cache: short"
|
|
145
|
+
return await db.query('SELECT * FROM nav_items');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Handler (uncached, calls ctx methods freely)
|
|
149
|
+
async function NavLayout(ctx) {
|
|
150
|
+
const navData = await getNavData();
|
|
151
|
+
ctx.set("navItems", navData); // Works -- outside "use cache"
|
|
152
|
+
return <Nav items={navData}><Outlet /></Nav>;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Misuse Guards
|
|
157
|
+
|
|
158
|
+
### Cannot use as middleware
|
|
159
|
+
|
|
160
|
+
Cached functions cannot be passed to `middleware()`. Middleware runs on every
|
|
161
|
+
request (onion model) and must not be cached.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// WRONG -- throws at boot time
|
|
165
|
+
middleware(cachedFn);
|
|
166
|
+
|
|
167
|
+
// RIGHT -- call cached function inside middleware
|
|
168
|
+
middleware(async (ctx, next) => {
|
|
169
|
+
const data = await getCachedData();
|
|
170
|
+
ctx.set("data", data);
|
|
171
|
+
await next();
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Cannot use as Static() handler
|
|
176
|
+
|
|
177
|
+
Static handlers render once at build time. `"use cache"` is redundant.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// WRONG -- throws at boot time
|
|
181
|
+
export const Page = Static(cachedFn);
|
|
182
|
+
|
|
183
|
+
// RIGHT -- remove "use cache", Static already caches
|
|
184
|
+
export const Page = Static(async (ctx) => {
|
|
185
|
+
return <div>Built once</div>;
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Cannot use as Prerender() handler or getParams
|
|
190
|
+
|
|
191
|
+
Prerender handlers render at build time. `"use cache"` is redundant.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// WRONG -- throws at boot time (handler)
|
|
195
|
+
export const Page = Prerender(getParams, cachedFn);
|
|
196
|
+
|
|
197
|
+
// WRONG -- throws at boot time (getParams)
|
|
198
|
+
export const Page = Prerender(cachedGetParams, handler);
|
|
199
|
+
|
|
200
|
+
// RIGHT -- remove "use cache"
|
|
201
|
+
export const Page = Prerender(
|
|
202
|
+
async () => [{ slug: "a" }],
|
|
203
|
+
async (ctx) => <Page slug={ctx.params.slug} />,
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Performance: waitUntil
|
|
208
|
+
|
|
209
|
+
On cache miss, the function executes and the result is serialized inline (blocking).
|
|
210
|
+
The cache **store write** (`setItem`) is deferred to `waitUntil` and does NOT block
|
|
211
|
+
the response.
|
|
212
|
+
|
|
213
|
+
On stale hit, stale data is returned immediately. Background revalidation (re-execute
|
|
214
|
+
|
|
215
|
+
- store) runs entirely inside `waitUntil`.
|
|
216
|
+
|
|
217
|
+
| Phase | Blocks response? |
|
|
218
|
+
| ------------------------------------ | ---------------- |
|
|
219
|
+
| Function execution (miss) | Yes |
|
|
220
|
+
| Result serialization (miss) | Yes |
|
|
221
|
+
| Cache store write (miss) | No (waitUntil) |
|
|
222
|
+
| Stale value return (stale hit) | No (immediate) |
|
|
223
|
+
| Background revalidation (stale) | No (waitUntil) |
|
|
224
|
+
| Cache lookup + deserialization (hit) | Yes (fast) |
|
|
225
|
+
|
|
226
|
+
## Using with Loaders
|
|
227
|
+
|
|
228
|
+
`"use cache"` works inside loaders. The loader runs every request, but the inner
|
|
229
|
+
cached function returns cached data:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Cached data function
|
|
233
|
+
export async function getProductData(slug: string) {
|
|
234
|
+
"use cache";
|
|
235
|
+
return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Loader runs every request, but inner call is cached
|
|
239
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
240
|
+
return getProductData(ctx.params.slug);
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Using with Intercepted Routes
|
|
245
|
+
|
|
246
|
+
Path handlers and intercept handlers have different `functionId` values from the
|
|
247
|
+
Vite transform, so they naturally get distinct cache entries even for the same URL:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Path handler -- cached separately
|
|
251
|
+
path("/product/:id", async (ctx) => {
|
|
252
|
+
"use cache"
|
|
253
|
+
return <FullProductPage id={ctx.params.id} />;
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
// Intercept handler -- cached separately (different functionId)
|
|
257
|
+
intercept("@modal", ".product", async (ctx) => {
|
|
258
|
+
"use cache"
|
|
259
|
+
return <ProductModal id={ctx.params.id} />;
|
|
260
|
+
}),
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Vite Transform
|
|
264
|
+
|
|
265
|
+
The `rango:use-cache` Vite plugin detects the directive and wraps exports with
|
|
266
|
+
`registerCachedFunction()`:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Input
|
|
270
|
+
"use cache"
|
|
271
|
+
export async function getProducts() { ... }
|
|
272
|
+
|
|
273
|
+
// Output
|
|
274
|
+
import { registerCachedFunction } from '@rangojs/router/cache-runtime';
|
|
275
|
+
export const getProducts = registerCachedFunction(
|
|
276
|
+
async function getProducts() { ... },
|
|
277
|
+
"src/data/products.ts#getProducts",
|
|
278
|
+
"default"
|
|
279
|
+
);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Function-level directives are hoisted:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Input
|
|
286
|
+
export async function getProducts() {
|
|
287
|
+
"use cache: short";
|
|
288
|
+
return await db.query("...");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Output
|
|
292
|
+
const __rango_cached_getProducts = registerCachedFunction(
|
|
293
|
+
async function getProducts() {
|
|
294
|
+
return await db.query("...");
|
|
295
|
+
},
|
|
296
|
+
"src/data/products.ts#getProducts",
|
|
297
|
+
"short",
|
|
298
|
+
);
|
|
299
|
+
export async function getProducts() {
|
|
300
|
+
return __rango_cached_getProducts();
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Backing Store
|
|
305
|
+
|
|
306
|
+
Writes to the same `SegmentCacheStore` as `cache()` DSL, `Static()`, and `Prerender()`.
|
|
307
|
+
One store, one configuration, one invalidation API. Tag-based invalidation
|
|
308
|
+
(`revalidateTag`) works across all mechanisms.
|
|
309
|
+
|
|
310
|
+
## Interaction with Other Caching
|
|
311
|
+
|
|
312
|
+
| Mechanism | Granularity | When | Use case |
|
|
313
|
+
| ------------------ | ------------------ | ---------- | ----------------------------------------------- |
|
|
314
|
+
| `"use cache"` | Function/component | Runtime | Cache individual data fetches or components |
|
|
315
|
+
| `cache()` DSL | Route segment | Runtime | Cache entire route subtrees with children |
|
|
316
|
+
| `cache('profile')` | Route segment | Runtime | Same as cache() with a named profile |
|
|
317
|
+
| `Static()` | Route segment | Build-time | Render once, never re-render |
|
|
318
|
+
| `Prerender()` | Route segment | Build-time | Pre-render known params, optional live fallback |
|
|
319
|
+
|
|
320
|
+
## Dev Mode
|
|
321
|
+
|
|
322
|
+
In development, the Vite transform still wraps functions, but the cache store is
|
|
323
|
+
a `MemorySegmentCacheStore` that works locally. Functions cache normally in dev
|
|
324
|
+
for testing cache behavior.
|
package/src/__internal.ts
CHANGED
|
@@ -108,7 +108,11 @@ export type {
|
|
|
108
108
|
* @internal
|
|
109
109
|
* Router context for AsyncLocalStorage.
|
|
110
110
|
*/
|
|
111
|
-
export type {
|
|
111
|
+
export type {
|
|
112
|
+
RouterContext,
|
|
113
|
+
RevalidationContext,
|
|
114
|
+
InterceptResult,
|
|
115
|
+
} from "./router/router-context.js";
|
|
112
116
|
|
|
113
117
|
// ============================================================================
|
|
114
118
|
// Match Pipeline (Internal)
|
|
@@ -118,7 +122,10 @@ export type { RouterContext, RevalidationContext, InterceptResult } from "./rout
|
|
|
118
122
|
* @internal
|
|
119
123
|
* Route match context during pipeline processing.
|
|
120
124
|
*/
|
|
121
|
-
export type {
|
|
125
|
+
export type {
|
|
126
|
+
MatchContext,
|
|
127
|
+
MatchPipelineState,
|
|
128
|
+
} from "./router/match-context.js";
|
|
122
129
|
|
|
123
130
|
/**
|
|
124
131
|
* @internal
|
|
@@ -153,7 +160,7 @@ export type {
|
|
|
153
160
|
/**
|
|
154
161
|
* @internal
|
|
155
162
|
* Internal handler context with additional props for router internals.
|
|
156
|
-
* Includes `
|
|
163
|
+
* Includes `_currentSegmentId` and `_responseType`.
|
|
157
164
|
*/
|
|
158
165
|
export type { InternalHandlerContext } from "./types.js";
|
|
159
166
|
|
|
@@ -172,4 +179,3 @@ export {
|
|
|
172
179
|
type SerializedEntry,
|
|
173
180
|
type SerializedManifest,
|
|
174
181
|
} from "./debug.js";
|
|
175
|
-
|
package/src/bin/rango.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { resolve, dirname } from "node:path";
|
|
2
|
+
import { readFileSync, statSync, existsSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
findTsFiles,
|
|
5
|
+
writePerModuleRouteTypesForFile,
|
|
6
|
+
writeCombinedRouteTypes,
|
|
7
|
+
detectUnresolvableIncludes,
|
|
8
|
+
detectUnresolvableIncludesForUrlsFile,
|
|
9
|
+
findNestedRouterConflict,
|
|
10
|
+
formatNestedRouterConflictError,
|
|
11
|
+
type UnresolvableInclude,
|
|
12
|
+
} from "../build/generate-route-types.ts";
|
|
13
|
+
|
|
14
|
+
const [command, ...rawArgs] = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
if (command === "generate") {
|
|
17
|
+
// Parse flags
|
|
18
|
+
let mode: "default" | "runtime" | "static" = "default";
|
|
19
|
+
let configFile: string | undefined;
|
|
20
|
+
const positionalArgs: string[] = [];
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
23
|
+
const arg = rawArgs[i];
|
|
24
|
+
if (arg === "--runtime") {
|
|
25
|
+
mode = "runtime";
|
|
26
|
+
} else if (arg === "--static") {
|
|
27
|
+
mode = "static";
|
|
28
|
+
} else if (arg === "--config") {
|
|
29
|
+
configFile = rawArgs[++i];
|
|
30
|
+
if (!configFile) {
|
|
31
|
+
console.error("[rango] --config requires a path argument");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
} else if (arg.startsWith("--")) {
|
|
35
|
+
console.error(`[rango] Unknown flag: ${arg}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
} else {
|
|
38
|
+
positionalArgs.push(arg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (positionalArgs.length === 0) {
|
|
43
|
+
console.error(
|
|
44
|
+
"[rango] Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]",
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (configFile && mode !== "runtime") {
|
|
50
|
+
console.warn("[rango] --config is only used with --runtime, ignoring");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === "runtime") {
|
|
54
|
+
// Runtime discovery: dynamically import to avoid loading Vite for static-only usage
|
|
55
|
+
runRuntimeDiscovery(positionalArgs, configFile).catch((err) => {
|
|
56
|
+
console.error(`[rango] Runtime discovery failed: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
runStaticGeneration(positionalArgs, mode);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
if (
|
|
64
|
+
command &&
|
|
65
|
+
command !== "help" &&
|
|
66
|
+
command !== "--help" &&
|
|
67
|
+
command !== "-h"
|
|
68
|
+
) {
|
|
69
|
+
console.error(`[rango] Unknown command: ${command}\n`);
|
|
70
|
+
}
|
|
71
|
+
console.log(`Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]
|
|
72
|
+
|
|
73
|
+
Auto-detects file type (createRouter, urls) and generates
|
|
74
|
+
the appropriate .gen.ts route type files.
|
|
75
|
+
|
|
76
|
+
Modes:
|
|
77
|
+
(default) Static parser with error on unresolvable includes
|
|
78
|
+
--runtime Vite-based runtime discovery (100% coverage)
|
|
79
|
+
Requires vite and @vitejs/plugin-rsc
|
|
80
|
+
--static Static parser, accept partial output with warnings
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
--config <path> Path to vite.config.ts (--runtime only, auto-detected if omitted)
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
rango generate src/router.tsx
|
|
87
|
+
rango generate src/router.tsx --runtime
|
|
88
|
+
rango generate src/ --static`);
|
|
89
|
+
process.exit(
|
|
90
|
+
command && command !== "help" && command !== "--help" && command !== "-h"
|
|
91
|
+
? 1
|
|
92
|
+
: 0,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Walk up from a file path to find the project root (directory containing
|
|
98
|
+
* package.json or vite.config.ts).
|
|
99
|
+
*/
|
|
100
|
+
function findProjectRoot(fromPath: string): string {
|
|
101
|
+
let dir = dirname(resolve(fromPath));
|
|
102
|
+
while (dir !== dirname(dir)) {
|
|
103
|
+
if (
|
|
104
|
+
existsSync(resolve(dir, "package.json")) ||
|
|
105
|
+
existsSync(resolve(dir, "vite.config.ts")) ||
|
|
106
|
+
existsSync(resolve(dir, "vite.config.js"))
|
|
107
|
+
) {
|
|
108
|
+
return dir;
|
|
109
|
+
}
|
|
110
|
+
dir = dirname(dir);
|
|
111
|
+
}
|
|
112
|
+
// Fallback to cwd if no project root found
|
|
113
|
+
return process.cwd();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runStaticGeneration(args: string[], mode: "default" | "static") {
|
|
117
|
+
// Expand args: files are used directly, directories are scanned
|
|
118
|
+
const files: string[] = [];
|
|
119
|
+
for (const arg of args) {
|
|
120
|
+
const resolved = resolve(arg);
|
|
121
|
+
try {
|
|
122
|
+
if (statSync(resolved).isDirectory()) {
|
|
123
|
+
files.push(...findTsFiles(resolved));
|
|
124
|
+
} else {
|
|
125
|
+
files.push(resolved);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
console.warn(`[rango] Skipping ${arg}: not found`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (files.length === 0) {
|
|
133
|
+
console.log("[rango] No files to process");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Phase 1: Classify files
|
|
138
|
+
const routerFiles: string[] = [];
|
|
139
|
+
const urlsFiles: string[] = [];
|
|
140
|
+
|
|
141
|
+
for (const filePath of files) {
|
|
142
|
+
try {
|
|
143
|
+
const source = readFileSync(filePath, "utf-8");
|
|
144
|
+
if (/\bcreateRouter\s*[<(]/.test(source)) {
|
|
145
|
+
routerFiles.push(filePath);
|
|
146
|
+
}
|
|
147
|
+
if (source.includes("urls(")) {
|
|
148
|
+
urlsFiles.push(filePath);
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[rango] Failed to process ${filePath}: ${(err as Error).message}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Phase 2: Collect diagnostics from all files BEFORE writing anything
|
|
158
|
+
const allDiagnostics: Array<UnresolvableInclude & { routerFile: string }> =
|
|
159
|
+
[];
|
|
160
|
+
|
|
161
|
+
for (const routerFile of routerFiles) {
|
|
162
|
+
const diagnostics = detectUnresolvableIncludes(routerFile);
|
|
163
|
+
for (const d of diagnostics) {
|
|
164
|
+
allDiagnostics.push({ ...d, routerFile });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Also check standalone urls files not covered by router-level detection
|
|
169
|
+
const routerFileSet = new Set(routerFiles);
|
|
170
|
+
for (const urlsFile of urlsFiles) {
|
|
171
|
+
if (routerFileSet.has(urlsFile)) continue;
|
|
172
|
+
const diagnostics = detectUnresolvableIncludesForUrlsFile(urlsFile);
|
|
173
|
+
for (const d of diagnostics) {
|
|
174
|
+
allDiagnostics.push({ ...d, routerFile: urlsFile });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Deduplicate diagnostics (router and urls detection may find the same issue)
|
|
179
|
+
const seen = new Set<string>();
|
|
180
|
+
const uniqueDiagnostics = allDiagnostics.filter((d) => {
|
|
181
|
+
const key = `${d.sourceFile}:${d.pathPrefix}:${d.reason}`;
|
|
182
|
+
if (seen.has(key)) return false;
|
|
183
|
+
seen.add(key);
|
|
184
|
+
return true;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (uniqueDiagnostics.length > 0 && mode === "default") {
|
|
188
|
+
// Hard error: no files written
|
|
189
|
+
console.error("\n[rango] Unresolvable includes detected:\n");
|
|
190
|
+
formatDiagnostics(uniqueDiagnostics);
|
|
191
|
+
console.error(
|
|
192
|
+
"\nThe static parser cannot resolve these includes because they use " +
|
|
193
|
+
"factory functions or dynamic expressions.\n\n" +
|
|
194
|
+
"Options:\n" +
|
|
195
|
+
" rango generate <path> --runtime Use Vite-based discovery (requires vite)\n" +
|
|
196
|
+
" rango generate <path> --static Accept partial output (missing routes above)\n",
|
|
197
|
+
);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (uniqueDiagnostics.length > 0 && mode === "static") {
|
|
202
|
+
// Warning: partial output accepted
|
|
203
|
+
console.warn(
|
|
204
|
+
"\n[rango] Warning: partial output (unresolvable includes):\n",
|
|
205
|
+
);
|
|
206
|
+
formatDiagnostics(uniqueDiagnostics);
|
|
207
|
+
console.warn("");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nestedRouterConflict = findNestedRouterConflict(routerFiles);
|
|
211
|
+
if (nestedRouterConflict) {
|
|
212
|
+
console.error(
|
|
213
|
+
`\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
|
|
214
|
+
);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Phase 3: Write all outputs (only reached if diagnostics pass or --static)
|
|
219
|
+
for (const urlsFile of urlsFiles) {
|
|
220
|
+
writePerModuleRouteTypesForFile(urlsFile);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const routerFile of routerFiles) {
|
|
224
|
+
const projectRoot = findProjectRoot(routerFile);
|
|
225
|
+
writeCombinedRouteTypes(projectRoot, [routerFile]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(
|
|
229
|
+
`[rango] Processed ${files.length} file(s)${routerFiles.length ? ` (${routerFiles.length} router)` : ""}`,
|
|
230
|
+
);
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runRuntimeDiscovery(args: string[], configFile?: string) {
|
|
235
|
+
// Resolve the entry: find the router file from the arguments
|
|
236
|
+
const files: string[] = [];
|
|
237
|
+
for (const arg of args) {
|
|
238
|
+
const resolved = resolve(arg);
|
|
239
|
+
try {
|
|
240
|
+
if (statSync(resolved).isDirectory()) {
|
|
241
|
+
files.push(...findTsFiles(resolved));
|
|
242
|
+
} else {
|
|
243
|
+
files.push(resolved);
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
console.warn(`[rango] Skipping ${arg}: not found`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Find router files among the inputs
|
|
251
|
+
const routerEntries: string[] = [];
|
|
252
|
+
for (const filePath of files) {
|
|
253
|
+
try {
|
|
254
|
+
const source = readFileSync(filePath, "utf-8");
|
|
255
|
+
if (/\bcreateRouter\s*[<(]/.test(source)) {
|
|
256
|
+
routerEntries.push(filePath);
|
|
257
|
+
}
|
|
258
|
+
// Also generate per-module types for urls files
|
|
259
|
+
if (source.includes("urls(")) {
|
|
260
|
+
writePerModuleRouteTypesForFile(filePath);
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
// Skip unreadable files
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (routerEntries.length === 0) {
|
|
268
|
+
console.error("[rango] No router files found in the provided paths");
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const nestedRouterConflict = findNestedRouterConflict(routerEntries);
|
|
273
|
+
if (nestedRouterConflict) {
|
|
274
|
+
console.error(
|
|
275
|
+
`\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
|
|
276
|
+
);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let discoverAndWriteRouteTypes: typeof import("../build/runtime-discovery.ts").discoverAndWriteRouteTypes;
|
|
281
|
+
try {
|
|
282
|
+
const mod = await import("../build/runtime-discovery.ts");
|
|
283
|
+
discoverAndWriteRouteTypes = mod.discoverAndWriteRouteTypes;
|
|
284
|
+
} catch (err: any) {
|
|
285
|
+
if (
|
|
286
|
+
err.code === "ERR_MODULE_NOT_FOUND" ||
|
|
287
|
+
err.code === "MODULE_NOT_FOUND"
|
|
288
|
+
) {
|
|
289
|
+
console.error(
|
|
290
|
+
"[rango] Runtime discovery requires 'vite' and '@vitejs/plugin-rsc'.\n" +
|
|
291
|
+
"Install them with: pnpm add -D vite @vitejs/plugin-rsc",
|
|
292
|
+
);
|
|
293
|
+
} else {
|
|
294
|
+
console.error(`[rango] Failed to load runtime discovery: ${err.message}`);
|
|
295
|
+
}
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const entry of routerEntries) {
|
|
300
|
+
const projectRoot = findProjectRoot(entry);
|
|
301
|
+
const result = await discoverAndWriteRouteTypes({
|
|
302
|
+
root: projectRoot,
|
|
303
|
+
configFile,
|
|
304
|
+
entry,
|
|
305
|
+
});
|
|
306
|
+
console.log(
|
|
307
|
+
`[rango] Runtime discovery: ${result.routerCount} router(s), ${result.routeCount} route(s)`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatDiagnostics(
|
|
313
|
+
diagnostics: Array<UnresolvableInclude & { routerFile: string }>,
|
|
314
|
+
) {
|
|
315
|
+
for (const d of diagnostics) {
|
|
316
|
+
const prefix = d.namePrefix ? `${d.namePrefix}.*` : `${d.pathPrefix}*`;
|
|
317
|
+
console.error(` ${prefix}`);
|
|
318
|
+
console.error(` Reason: ${d.reason} -- ${d.detail}`);
|
|
319
|
+
console.error(` Source: ${d.sourceFile}`);
|
|
320
|
+
}
|
|
321
|
+
}
|