@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-setup
|
|
3
|
+
description: Create and configure the RSC router with createRouter
|
|
4
|
+
argument-hint: [option]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Router Setup with createRouter
|
|
8
|
+
|
|
9
|
+
## Basic Router Creation
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/router.tsx
|
|
13
|
+
import { createRouter } from "@rangojs/router";
|
|
14
|
+
import { Document } from "./document";
|
|
15
|
+
import { urlpatterns } from "./urls";
|
|
16
|
+
|
|
17
|
+
const router = createRouter({
|
|
18
|
+
document: Document,
|
|
19
|
+
urls: urlpatterns,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default router;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## URL Patterns (Django-style)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// src/urls.tsx
|
|
29
|
+
import { urls } from "@rangojs/router";
|
|
30
|
+
import { HomePage } from "./pages/home";
|
|
31
|
+
import { AboutPage } from "./pages/about";
|
|
32
|
+
import { ProductPage } from "./pages/product";
|
|
33
|
+
import { RootLayout } from "./layouts/RootLayout";
|
|
34
|
+
|
|
35
|
+
export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
36
|
+
path("/", HomePage, { name: "home" }),
|
|
37
|
+
path("/about", AboutPage, { name: "about" }),
|
|
38
|
+
|
|
39
|
+
layout(<RootLayout />, () => [
|
|
40
|
+
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
41
|
+
loader(ProductLoader),
|
|
42
|
+
loading(<ProductSkeleton />),
|
|
43
|
+
]),
|
|
44
|
+
]),
|
|
45
|
+
]);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## The urls() DSL
|
|
49
|
+
|
|
50
|
+
The `urls()` function provides a callback with all available DSL functions:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
urls(({
|
|
54
|
+
path, // Define a route
|
|
55
|
+
layout, // Wrap routes in a layout
|
|
56
|
+
parallel, // Define parallel routes (slots)
|
|
57
|
+
loader, // Add data loader
|
|
58
|
+
loading, // Add loading skeleton
|
|
59
|
+
cache, // Configure caching
|
|
60
|
+
middleware, // Add middleware
|
|
61
|
+
revalidate, // Control revalidation
|
|
62
|
+
intercept, // Intercept routes for modals
|
|
63
|
+
when, // Conditional rendering
|
|
64
|
+
}) => [
|
|
65
|
+
// Route definitions here
|
|
66
|
+
]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Router Options
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
interface RSCRouterOptions<TEnv> {
|
|
73
|
+
// URL patterns from urls() function
|
|
74
|
+
urls: UrlPatterns;
|
|
75
|
+
|
|
76
|
+
// Document component wrapping entire app
|
|
77
|
+
document?: ComponentType<{ children: ReactNode }>;
|
|
78
|
+
|
|
79
|
+
// Enable performance metrics
|
|
80
|
+
debugPerformance?: boolean;
|
|
81
|
+
|
|
82
|
+
// Default error boundary
|
|
83
|
+
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
|
|
84
|
+
|
|
85
|
+
// Default not-found boundary
|
|
86
|
+
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
|
|
87
|
+
|
|
88
|
+
// Component for 404 routes
|
|
89
|
+
notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
90
|
+
|
|
91
|
+
// Error logging callback
|
|
92
|
+
onError?: OnErrorCallback<TEnv>;
|
|
93
|
+
|
|
94
|
+
// Global cache configuration
|
|
95
|
+
cache?: CacheConfig<TEnv>;
|
|
96
|
+
|
|
97
|
+
// Theme configuration
|
|
98
|
+
theme?: ThemeConfig | true;
|
|
99
|
+
|
|
100
|
+
// Connection warmup (default: true)
|
|
101
|
+
warmup?: boolean;
|
|
102
|
+
|
|
103
|
+
// CSP nonce provider (for router.fetch)
|
|
104
|
+
nonce?: (request: Request, env: TEnv) => string | true | Promise<string | true>;
|
|
105
|
+
|
|
106
|
+
// RSC version string (for router.fetch)
|
|
107
|
+
version?: string;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Using the Request Handler
|
|
112
|
+
|
|
113
|
+
The router provides a `fetch` method to handle RSC requests:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// src/router.tsx
|
|
117
|
+
import { createRouter } from "@rangojs/router";
|
|
118
|
+
import { Document } from "./document";
|
|
119
|
+
import { urlpatterns } from "./urls";
|
|
120
|
+
|
|
121
|
+
export const router = createRouter({
|
|
122
|
+
document: Document,
|
|
123
|
+
urls: urlpatterns,
|
|
124
|
+
nonce: () => true, // Auto-generate nonce for CSP
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// src/worker.tsx (Cloudflare Workers)
|
|
128
|
+
import { router } from "./router";
|
|
129
|
+
|
|
130
|
+
export default { fetch: router.fetch };
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Document Component
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// src/document.tsx
|
|
137
|
+
import type { ReactNode } from "react";
|
|
138
|
+
|
|
139
|
+
export function Document({ children }: { children: ReactNode }) {
|
|
140
|
+
return (
|
|
141
|
+
<html lang="en">
|
|
142
|
+
<head>
|
|
143
|
+
<meta charSet="utf-8" />
|
|
144
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
145
|
+
<title>My App</title>
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<div id="root">{children}</div>
|
|
149
|
+
</body>
|
|
150
|
+
</html>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Using with Cloudflare Workers
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// src/router.tsx
|
|
159
|
+
import { createRouter } from "@rangojs/router";
|
|
160
|
+
import { Document } from "./document";
|
|
161
|
+
import { urlpatterns } from "./urls";
|
|
162
|
+
|
|
163
|
+
export const router = createRouter<AppEnv>({
|
|
164
|
+
document: Document,
|
|
165
|
+
urls: urlpatterns,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// src/worker.tsx
|
|
169
|
+
import { router } from "./router";
|
|
170
|
+
|
|
171
|
+
export default {
|
|
172
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
173
|
+
return router.fetch(request, { Bindings: env, Variables: {}, ctx });
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### With Dynamic Cache Configuration
|
|
179
|
+
|
|
180
|
+
For per-request cache configuration (e.g., Cloudflare Workers with ExecutionContext):
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// src/router.tsx
|
|
184
|
+
import { createRouter } from "@rangojs/router";
|
|
185
|
+
import { CFCacheStore } from "@rangojs/router/cache";
|
|
186
|
+
|
|
187
|
+
export const router = createRouter<AppEnv>({
|
|
188
|
+
document: Document,
|
|
189
|
+
urls: urlpatterns,
|
|
190
|
+
// Cache config receives env with ctx for ExecutionContext access
|
|
191
|
+
cache: (env) => ({
|
|
192
|
+
store: new CFCacheStore({ ctx: env.ctx, defaults: { ttl: 60 } }),
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// src/worker.tsx
|
|
197
|
+
import { router } from "./router";
|
|
198
|
+
|
|
199
|
+
export default {
|
|
200
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
201
|
+
return router.fetch(request, { Bindings: env, Variables: {}, ctx });
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Complete Example
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// src/urls.tsx
|
|
210
|
+
import { urls } from "@rangojs/router";
|
|
211
|
+
import { Outlet } from "@rangojs/router/client";
|
|
212
|
+
|
|
213
|
+
// Pages
|
|
214
|
+
import { HomePage } from "./pages/home";
|
|
215
|
+
import { AboutPage } from "./pages/about";
|
|
216
|
+
import { BlogIndexPage, BlogPostPage } from "./pages/blog";
|
|
217
|
+
|
|
218
|
+
// Layouts
|
|
219
|
+
import { RootLayout } from "./layouts/RootLayout";
|
|
220
|
+
import { BlogLayout } from "./layouts/BlogLayout";
|
|
221
|
+
|
|
222
|
+
// Loaders
|
|
223
|
+
import { BlogPostLoader, BlogSidebarLoader } from "./loaders/blog";
|
|
224
|
+
|
|
225
|
+
export const urlpatterns = urls(({ path, layout, parallel, loader, loading, cache }) => [
|
|
226
|
+
// Simple routes
|
|
227
|
+
path("/", HomePage, { name: "home" }),
|
|
228
|
+
path("/about", AboutPage, { name: "about" }),
|
|
229
|
+
|
|
230
|
+
// Blog with layout and loaders
|
|
231
|
+
layout(<BlogLayout />, () => [
|
|
232
|
+
// Sidebar as parallel route
|
|
233
|
+
parallel({ "@sidebar": () => <BlogSidebar /> }, () => [
|
|
234
|
+
loader(BlogSidebarLoader),
|
|
235
|
+
]),
|
|
236
|
+
|
|
237
|
+
// Cached blog routes
|
|
238
|
+
cache({ ttl: 60 }, () => [
|
|
239
|
+
path("/blog", BlogIndexPage, { name: "blog" }),
|
|
240
|
+
path("/blog/:slug", BlogPostPage, { name: "blogPost" }, () => [
|
|
241
|
+
loader(BlogPostLoader),
|
|
242
|
+
loading(<BlogPostSkeleton />),
|
|
243
|
+
]),
|
|
244
|
+
]),
|
|
245
|
+
]),
|
|
246
|
+
]);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// src/router.tsx
|
|
251
|
+
import { createRouter } from "@rangojs/router";
|
|
252
|
+
import { Document } from "./document";
|
|
253
|
+
import { urlpatterns } from "./urls";
|
|
254
|
+
|
|
255
|
+
const router = createRouter({
|
|
256
|
+
document: Document,
|
|
257
|
+
urls: urlpatterns,
|
|
258
|
+
|
|
259
|
+
defaultErrorBoundary: ({ error, reset }) => (
|
|
260
|
+
<div>
|
|
261
|
+
<h1>Something went wrong</h1>
|
|
262
|
+
<button onClick={reset}>Try again</button>
|
|
263
|
+
</div>
|
|
264
|
+
),
|
|
265
|
+
|
|
266
|
+
notFound: ({ pathname }) => (
|
|
267
|
+
<div>
|
|
268
|
+
<h1>404</h1>
|
|
269
|
+
<p>Page not found: {pathname}</p>
|
|
270
|
+
</div>
|
|
271
|
+
),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
export default router;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Including Sub-patterns
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// src/urls/shop.tsx
|
|
281
|
+
import { urls } from "@rangojs/router";
|
|
282
|
+
|
|
283
|
+
export const shopPatterns = urls(({ path, layout }) => [
|
|
284
|
+
path("/", ShopIndex, { name: "index" }),
|
|
285
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
// src/urls.tsx
|
|
289
|
+
import { urls, include } from "@rangojs/router";
|
|
290
|
+
import { shopPatterns } from "./urls/shop";
|
|
291
|
+
|
|
292
|
+
export const urlpatterns = urls(({ path }) => [
|
|
293
|
+
path("/", HomePage, { name: "home" }),
|
|
294
|
+
include("/shop", shopPatterns, { name: "shop" }),
|
|
295
|
+
]);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Environment Types
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import type { RouterEnv } from "@rangojs/router";
|
|
302
|
+
|
|
303
|
+
interface AppBindings {
|
|
304
|
+
DB: D1Database;
|
|
305
|
+
KV: KVNamespace;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface AppVariables {
|
|
309
|
+
user?: { id: string; name: string };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
type AppEnv = RouterEnv<AppBindings, AppVariables>;
|
|
313
|
+
|
|
314
|
+
const router = createRouter<AppEnv>({
|
|
315
|
+
document: Document,
|
|
316
|
+
urls: urlpatterns,
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Connection Warmup
|
|
321
|
+
|
|
322
|
+
Enabled by default. Keeps TCP+TLS connections alive so navigations after idle periods
|
|
323
|
+
don't pay handshake costs.
|
|
324
|
+
|
|
325
|
+
After 60s of no user interaction, the connection is marked cold. When the user returns
|
|
326
|
+
(tab becomes visible or first mouse/touch), a `HEAD ?_rsc_warmup` request re-establishes
|
|
327
|
+
the TLS connection before the next navigation. The server responds with 204 No Content
|
|
328
|
+
before any middleware or routing runs.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// Enabled by default
|
|
332
|
+
const router = createRouter({
|
|
333
|
+
document: Document,
|
|
334
|
+
urls: urlpatterns,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Disable warmup
|
|
338
|
+
const router = createRouter({
|
|
339
|
+
document: Document,
|
|
340
|
+
urls: urlpatterns,
|
|
341
|
+
warmup: false,
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The warmup request is relative to the current page path, so it works correctly
|
|
346
|
+
with subpath deployments (reverse proxy, base path).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tailwind
|
|
3
|
+
description: Set up Tailwind CSS v4 with the Document component and CSS imports
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tailwind CSS
|
|
8
|
+
|
|
9
|
+
Set up Tailwind CSS v4 with the Rango router. Styles are loaded through the Document component using Vite's `?url` CSS import.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -D tailwindcss @tailwindcss/vite
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Vite Plugin
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// vite.config.ts
|
|
21
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
plugins: [
|
|
25
|
+
tailwindcss(),
|
|
26
|
+
// ... other plugins
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## CSS Entry Point
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
/* src/index.css */
|
|
35
|
+
@import "tailwindcss";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Document Component
|
|
39
|
+
|
|
40
|
+
Import the CSS file with `?url` to get a hashed URL, then preload and link it in `<head>`:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// src/document.tsx
|
|
44
|
+
"use client";
|
|
45
|
+
|
|
46
|
+
import type { ReactNode } from "react";
|
|
47
|
+
import { MetaTags } from "@rangojs/router/client";
|
|
48
|
+
import styles from "./index.css?url";
|
|
49
|
+
|
|
50
|
+
export function Document({ children }: { children: ReactNode }) {
|
|
51
|
+
return (
|
|
52
|
+
<html lang="en">
|
|
53
|
+
<head>
|
|
54
|
+
<link rel="preload" href={styles} as="style" />
|
|
55
|
+
<link rel="stylesheet" href={styles} />
|
|
56
|
+
<MetaTags />
|
|
57
|
+
</head>
|
|
58
|
+
<body className="font-sans antialiased text-slate-900 bg-slate-50">
|
|
59
|
+
{children}
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `?url` suffix tells Vite to return the processed CSS file's URL instead of injecting it as a side effect. This gives you a stable, hashed asset path that works in both development and production.
|
|
67
|
+
|
|
68
|
+
## Customizing the Theme
|
|
69
|
+
|
|
70
|
+
Tailwind v4 uses CSS `@theme` for customization:
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
/* src/index.css */
|
|
74
|
+
@import "tailwindcss";
|
|
75
|
+
|
|
76
|
+
@theme {
|
|
77
|
+
--font-sans: "Inter", system-ui, sans-serif;
|
|
78
|
+
--color-primary: #3b82f6;
|
|
79
|
+
--color-secondary: #64748b;
|
|
80
|
+
--breakpoint-3xl: 1920px;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Dark Mode
|
|
85
|
+
|
|
86
|
+
Combine with the Rango theme system (see `/theme`):
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const router = createRouter({
|
|
90
|
+
document: Document,
|
|
91
|
+
urls: urlpatterns,
|
|
92
|
+
theme: { attribute: "class" },
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then use Tailwind's `dark:` variant which reads the `class` attribute:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<div className="bg-white dark:bg-slate-900 text-slate-900 dark:text-white">
|
|
100
|
+
Content
|
|
101
|
+
</div>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## With Custom Fonts
|
|
105
|
+
|
|
106
|
+
Use `@fontsource-variable` for self-hosted fonts bundled by Vite (see `/fonts` for all options):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pnpm add @fontsource-variable/inter
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
/* src/index.css */
|
|
114
|
+
@import "@fontsource-variable/inter";
|
|
115
|
+
@import "tailwindcss";
|
|
116
|
+
|
|
117
|
+
@theme {
|
|
118
|
+
--font-sans: "Inter Variable", system-ui, sans-serif;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
No extra `<link>` tags needed in the Document -- Vite bundles the font files from `node_modules` automatically.
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- `?url` import is required -- bare CSS imports inject styles as a side effect and do not work with SSR streaming
|
|
127
|
+
- `<link rel="preload" as="style">` eliminates render-blocking by starting the download early
|
|
128
|
+
- Tailwind v4 does not need a `tailwind.config.js` -- use `@theme` in CSS instead
|
|
129
|
+
- The `@tailwindcss/vite` plugin handles content detection automatically
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theme
|
|
3
|
+
description: Opt-in theme system with FOUC prevention for light/dark mode
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Theme Support
|
|
8
|
+
|
|
9
|
+
Opt-in theme system with FOUC prevention.
|
|
10
|
+
|
|
11
|
+
## Enable
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createRouter } from "@rangojs/router";
|
|
15
|
+
|
|
16
|
+
// Simple - all defaults
|
|
17
|
+
const router = createRouter<Env>({
|
|
18
|
+
document: Document,
|
|
19
|
+
urls: urlpatterns,
|
|
20
|
+
theme: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Custom config
|
|
24
|
+
const router = createRouter<Env>({
|
|
25
|
+
document: Document,
|
|
26
|
+
urls: urlpatterns,
|
|
27
|
+
theme: {
|
|
28
|
+
defaultTheme: "system", // "light" | "dark" | "system"
|
|
29
|
+
themes: ["light", "dark"],
|
|
30
|
+
attribute: "class", // or "data-theme"
|
|
31
|
+
storageKey: "theme",
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Server (in loaders/middleware)
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { createLoader, createMiddleware } from "@rangojs/router";
|
|
40
|
+
|
|
41
|
+
// In a loader
|
|
42
|
+
export const SettingsLoader = createLoader("settings", async (ctx) => {
|
|
43
|
+
const currentTheme = ctx.theme; // read from cookie
|
|
44
|
+
return { theme: currentTheme };
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// In middleware
|
|
48
|
+
export const themeMiddleware = createMiddleware(async (ctx, next) => {
|
|
49
|
+
// Set theme based on user preference
|
|
50
|
+
ctx.setTheme("dark");
|
|
51
|
+
await next();
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Client
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
"use client";
|
|
59
|
+
import { useTheme } from "@rangojs/router/theme";
|
|
60
|
+
|
|
61
|
+
function ThemeToggle() {
|
|
62
|
+
const { theme, setTheme, resolvedTheme, systemTheme, themes } = useTheme();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
|
|
66
|
+
<option value="system">System</option>
|
|
67
|
+
<option value="light">Light</option>
|
|
68
|
+
<option value="dark">Dark</option>
|
|
69
|
+
</select>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- `<MetaTags />` auto-renders inline script for FOUC prevention
|
|
77
|
+
- Add `suppressHydrationWarning` to `<html>`
|
|
78
|
+
- Theme persists in localStorage + cookie (for SSR)
|