@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
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 +9 -0
- package/README.md +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -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 +6 -1
- 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 +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- 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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +342 -0
- package/src/cache/cache-scope.ts +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -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 +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -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 +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +497 -0
- package/src/vite/router-discovery.ts +1423 -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/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- 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/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -15,10 +15,9 @@ argument-hint: [setup]
|
|
|
15
15
|
import { createRouter } from "@rangojs/router";
|
|
16
16
|
import { urlpatterns } from "./urls";
|
|
17
17
|
|
|
18
|
-
const router = createRouter<
|
|
18
|
+
const router = createRouter<AppBindings>({
|
|
19
19
|
document: Document,
|
|
20
|
-
|
|
21
|
-
});
|
|
20
|
+
}).routes(urlpatterns);
|
|
22
21
|
|
|
23
22
|
// Server-side named-route reverse (type-safe via routeMap)
|
|
24
23
|
export const reverse = router.reverse;
|
|
@@ -26,6 +25,39 @@ export const reverse = router.reverse;
|
|
|
26
25
|
export default router;
|
|
27
26
|
```
|
|
28
27
|
|
|
28
|
+
### Which global type should I use?
|
|
29
|
+
|
|
30
|
+
Use the generated route map by default. Manual `RegisteredRoutes` augmentation
|
|
31
|
+
is only needed when you want the richer `typeof router.routeMap` shape
|
|
32
|
+
available globally.
|
|
33
|
+
|
|
34
|
+
- `GeneratedRouteMap` — auto-registered by `router.named-routes.gen.ts`
|
|
35
|
+
Use for `Handler<"name">`, `Prerender<"name">`, server `ctx.reverse()`,
|
|
36
|
+
and named-route param/search inference.
|
|
37
|
+
- `typeof router.routeMap` — the real merged route map from your router
|
|
38
|
+
instance, including response-route metadata such as `{ path, response }`.
|
|
39
|
+
- `RegisteredRoutes` — manual global hook for exposing `typeof router.routeMap`
|
|
40
|
+
to utilities like `href()`, `ValidPaths`, and `PathResponse`.
|
|
41
|
+
|
|
42
|
+
Recommended setup:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// router.tsx
|
|
46
|
+
import { createRouter } from "@rangojs/router";
|
|
47
|
+
import { urlpatterns } from "./urls";
|
|
48
|
+
import type { AppBindings, AppVars } from "./env";
|
|
49
|
+
|
|
50
|
+
export const router = createRouter<AppBindings>({}).routes(urlpatterns);
|
|
51
|
+
|
|
52
|
+
declare global {
|
|
53
|
+
namespace RSCRouter {
|
|
54
|
+
interface Env extends AppBindings {}
|
|
55
|
+
interface Vars extends AppVars {}
|
|
56
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
29
61
|
## Route Definition with Type-Safe Names
|
|
30
62
|
|
|
31
63
|
```typescript
|
|
@@ -45,22 +77,35 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
45
77
|
|
|
46
78
|
## Type-Safe href()
|
|
47
79
|
|
|
48
|
-
### Server: ctx.reverse
|
|
80
|
+
### Server: ctx.reverse with route names
|
|
49
81
|
|
|
50
|
-
In route handlers,
|
|
82
|
+
In route handlers, `ctx.reverse()` uses two namespaces:
|
|
83
|
+
|
|
84
|
+
- **`.name`** — local route, resolved within the current `include()` scope
|
|
85
|
+
- **`name`** — global route, from the named-routes definition
|
|
51
86
|
|
|
52
87
|
```typescript
|
|
53
|
-
import {
|
|
88
|
+
import type { Handler } from "@rangojs/router";
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
90
|
+
export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
91
|
+
ctx.reverse(".cart"); // Local: /shop/cart
|
|
92
|
+
ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
|
|
93
|
+
ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
|
|
94
|
+
};
|
|
95
|
+
```
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reverse("blog.post"); // Absolute names always allowed
|
|
97
|
+
For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
|
|
98
|
+
and pass it as the second generic to `Handler` or `Prerender`:
|
|
61
99
|
|
|
62
|
-
|
|
63
|
-
|
|
100
|
+
```typescript
|
|
101
|
+
import type { Handler } from "@rangojs/router";
|
|
102
|
+
import type { routes } from "./shop.gen.js";
|
|
103
|
+
|
|
104
|
+
export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
|
|
105
|
+
ctx.reverse(".cart"); // Type-safe local name
|
|
106
|
+
ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
|
|
107
|
+
ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
|
|
108
|
+
};
|
|
64
109
|
```
|
|
65
110
|
|
|
66
111
|
### Client: href + useHref
|
|
@@ -82,6 +127,17 @@ function ShopNav() {
|
|
|
82
127
|
}
|
|
83
128
|
```
|
|
84
129
|
|
|
130
|
+
`href()` and path-based response utilities read from `RegisteredRoutes`, so if
|
|
131
|
+
you want them typed globally you should augment:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
declare global {
|
|
135
|
+
namespace RSCRouter {
|
|
136
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
85
141
|
See `/links` for full URL generation guide.
|
|
86
142
|
|
|
87
143
|
## Environment Type Setup
|
|
@@ -90,50 +146,57 @@ Define your app's environment for type-safe bindings and variables:
|
|
|
90
146
|
|
|
91
147
|
```typescript
|
|
92
148
|
// env.ts
|
|
93
|
-
import type { RouterEnv } from "@rangojs/router";
|
|
94
149
|
|
|
95
|
-
// Cloudflare bindings
|
|
96
|
-
interface AppBindings {
|
|
150
|
+
// Cloudflare bindings — passed as TEnv to createRouter<TEnv>()
|
|
151
|
+
export interface AppBindings {
|
|
97
152
|
DB: D1Database;
|
|
98
153
|
KV: KVNamespace;
|
|
99
154
|
CACHE: KVNamespace;
|
|
100
155
|
AI: Ai;
|
|
101
156
|
}
|
|
102
157
|
|
|
103
|
-
// Variables set by middleware
|
|
104
|
-
interface AppVariables {
|
|
158
|
+
// Variables set by middleware — declared via module augmentation
|
|
159
|
+
export interface AppVariables {
|
|
105
160
|
user?: { id: string; email: string; role: string };
|
|
106
161
|
requestId?: string;
|
|
107
162
|
permissions?: string[];
|
|
108
163
|
}
|
|
109
|
-
|
|
110
|
-
// Combined environment type
|
|
111
|
-
export type AppEnv = RouterEnv<AppBindings, AppVariables>;
|
|
112
164
|
```
|
|
113
165
|
|
|
114
166
|
### Using Environment Types
|
|
115
167
|
|
|
116
168
|
```typescript
|
|
117
169
|
// router.tsx
|
|
118
|
-
import type {
|
|
170
|
+
import type { AppBindings, AppVariables } from "./env";
|
|
119
171
|
|
|
120
|
-
const router = createRouter<
|
|
172
|
+
const router = createRouter<AppBindings>({
|
|
121
173
|
document: Document,
|
|
122
|
-
|
|
123
|
-
|
|
174
|
+
}).routes(urlpatterns);
|
|
175
|
+
|
|
176
|
+
// Register bindings and variables globally for implicit typing
|
|
177
|
+
declare global {
|
|
178
|
+
namespace RSCRouter {
|
|
179
|
+
interface Env extends AppBindings {}
|
|
180
|
+
interface Vars extends AppVariables {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
124
183
|
|
|
125
|
-
// middleware - typed ctx.
|
|
126
|
-
import {
|
|
184
|
+
// middleware - typed via ctx.set / ctx.get
|
|
185
|
+
import type { Middleware } from "@rangojs/router";
|
|
127
186
|
|
|
128
|
-
export const authMiddleware =
|
|
129
|
-
ctx.
|
|
187
|
+
export const authMiddleware: Middleware = async (ctx, next) => {
|
|
188
|
+
ctx.set("user", {
|
|
189
|
+
id: "123",
|
|
190
|
+
email: "user@example.com",
|
|
191
|
+
role: "admin",
|
|
192
|
+
});
|
|
130
193
|
await next();
|
|
131
|
-
}
|
|
194
|
+
};
|
|
132
195
|
|
|
133
196
|
// loaders - typed context
|
|
134
|
-
export const UserLoader = createLoader(
|
|
135
|
-
const db = ctx.env.
|
|
136
|
-
const userId = ctx.
|
|
197
|
+
export const UserLoader = createLoader(async (ctx) => {
|
|
198
|
+
const db = ctx.env.DB; // D1Database (plain bindings)
|
|
199
|
+
const userId = ctx.get("user")?.id; // from RSCRouter.Vars
|
|
137
200
|
return db.prepare("SELECT * FROM users WHERE id = ?").bind(userId).first();
|
|
138
201
|
});
|
|
139
202
|
```
|
|
@@ -146,7 +209,8 @@ Register environment types globally for implicit typing:
|
|
|
146
209
|
// router.tsx
|
|
147
210
|
declare global {
|
|
148
211
|
namespace RSCRouter {
|
|
149
|
-
interface Env extends
|
|
212
|
+
interface Env extends AppBindings {}
|
|
213
|
+
interface Vars extends AppVariables {}
|
|
150
214
|
}
|
|
151
215
|
}
|
|
152
216
|
```
|
|
@@ -155,21 +219,119 @@ Now handlers have typed context without explicit imports:
|
|
|
155
219
|
|
|
156
220
|
```typescript
|
|
157
221
|
// In loaders
|
|
158
|
-
export const DashboardLoader = createLoader(
|
|
159
|
-
// ctx.env.
|
|
160
|
-
// ctx.
|
|
161
|
-
const user = ctx.
|
|
222
|
+
export const DashboardLoader = createLoader(async (ctx) => {
|
|
223
|
+
// ctx.env.DB is typed from global RSCRouter.Env
|
|
224
|
+
// ctx.get("user") is typed from global RSCRouter.Vars
|
|
225
|
+
const user = ctx.get("user");
|
|
162
226
|
return { user };
|
|
163
227
|
});
|
|
164
228
|
```
|
|
165
229
|
|
|
230
|
+
## Typed Search Params
|
|
231
|
+
|
|
232
|
+
Add a `search` schema to `path()` options for type-safe query parameters:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Route definition with search schema
|
|
236
|
+
path("/search", SearchPage, {
|
|
237
|
+
name: "search",
|
|
238
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Handler with typed search params
|
|
243
|
+
|
|
244
|
+
`Handler<"name">` automatically resolves route params and search params from the
|
|
245
|
+
global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// pages/search.tsx
|
|
249
|
+
import type { Handler } from "@rangojs/router";
|
|
250
|
+
|
|
251
|
+
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
252
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
253
|
+
const { q, page, sort } = ctx.search;
|
|
254
|
+
return <SearchResults q={q} page={page} sort={sort} />;
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
|
|
259
|
+
(from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
|
|
260
|
+
|
|
261
|
+
You can also pass an explicit route map for per-module isolation (opt-in,
|
|
262
|
+
after running `npx rango generate`):
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import type { Handler } from "@rangojs/router";
|
|
266
|
+
import type { routes } from "./urls.gen.js";
|
|
267
|
+
|
|
268
|
+
export const SearchPage: Handler<"search", routes> = (ctx) => { ... };
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
272
|
+
Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
|
|
273
|
+
Routes without a `search` schema keep the standard `URLSearchParams` behavior.
|
|
274
|
+
|
|
275
|
+
### RouteSearchParams and RouteParams utility types
|
|
276
|
+
|
|
277
|
+
Extract typed params by route name for use in component props, return types, or anywhere:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
281
|
+
|
|
282
|
+
// RouteSearchParams<"name"> resolves the search schema to a typed object
|
|
283
|
+
type SP = RouteSearchParams<"search">;
|
|
284
|
+
// { q: string | undefined; page?: number; sort?: string }
|
|
285
|
+
|
|
286
|
+
// RouteParams<"name"> resolves URL params from the route pattern
|
|
287
|
+
type P = RouteParams<"blogPost">;
|
|
288
|
+
// { slug: string }
|
|
289
|
+
|
|
290
|
+
// Optional URL params (`:slug?`) resolve to `string | undefined`
|
|
291
|
+
// because absent segments are omitted from `ctx.params` at runtime.
|
|
292
|
+
type C = RouteParams<"checkout">;
|
|
293
|
+
// { step?: string }
|
|
294
|
+
// → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
|
|
295
|
+
|
|
296
|
+
// Use in component props
|
|
297
|
+
interface SearchResultsProps {
|
|
298
|
+
params: RouteSearchParams<"search">;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
|
|
303
|
+
Pass an explicit route map as the second type argument when needed:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import type { routes } from "./urls.gen.js";
|
|
307
|
+
|
|
308
|
+
type SP = RouteSearchParams<"search", routes>;
|
|
309
|
+
type P = RouteParams<"blogPost", routes>;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Generated route types
|
|
313
|
+
|
|
314
|
+
In the generated `router.named-routes.gen.ts`, routes with search schemas
|
|
315
|
+
use `{ path, search }` objects:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// router.named-routes.gen.ts (auto-generated)
|
|
319
|
+
export const NamedRoutes = {
|
|
320
|
+
"search.index": {
|
|
321
|
+
path: "/search",
|
|
322
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
323
|
+
},
|
|
324
|
+
"home.index": "/", // No search schema -> plain string
|
|
325
|
+
} as const;
|
|
326
|
+
```
|
|
327
|
+
|
|
166
328
|
## Loader Type Safety
|
|
167
329
|
|
|
168
330
|
Loaders have typed return values:
|
|
169
331
|
|
|
170
332
|
```typescript
|
|
171
333
|
// loaders/product.ts
|
|
172
|
-
export const ProductLoader = createLoader(
|
|
334
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
173
335
|
return {
|
|
174
336
|
id: ctx.params.slug,
|
|
175
337
|
name: "Widget",
|
|
@@ -178,7 +340,7 @@ export const ProductLoader = createLoader("product", async (ctx) => {
|
|
|
178
340
|
});
|
|
179
341
|
|
|
180
342
|
// In server component - type is inferred
|
|
181
|
-
import { useLoader } from "@rangojs/router";
|
|
343
|
+
import { useLoader } from "@rangojs/router/client";
|
|
182
344
|
|
|
183
345
|
async function ProductPage() {
|
|
184
346
|
const product = await useLoader(ProductLoader);
|
|
@@ -188,39 +350,110 @@ async function ProductPage() {
|
|
|
188
350
|
|
|
189
351
|
// In client component - same type
|
|
190
352
|
"use client";
|
|
191
|
-
import {
|
|
353
|
+
import { useLoader } from "@rangojs/router/client";
|
|
192
354
|
|
|
193
355
|
function ProductPrice() {
|
|
194
|
-
const {
|
|
195
|
-
//
|
|
356
|
+
const { data } = useLoader(ProductLoader);
|
|
357
|
+
// data: { id: string; name: string; price: number }
|
|
358
|
+
const product = data;
|
|
196
359
|
return <span>${product.price}</span>;
|
|
197
360
|
}
|
|
198
361
|
```
|
|
199
362
|
|
|
200
|
-
##
|
|
363
|
+
## Typed Context Variables
|
|
201
364
|
|
|
202
|
-
|
|
365
|
+
`createVar<T>()` creates a typed token for `ctx.set()`/`ctx.get()`, making
|
|
366
|
+
handler-to-layout data contracts explicit and compile-time verified:
|
|
203
367
|
|
|
204
368
|
```typescript
|
|
205
|
-
|
|
206
|
-
import { createHandle } from "@rangojs/router";
|
|
369
|
+
import { createVar } from "@rangojs/router";
|
|
207
370
|
|
|
208
|
-
|
|
371
|
+
// Define a typed token (shared between producer and consumer)
|
|
372
|
+
interface PaginationData {
|
|
373
|
+
current: number;
|
|
374
|
+
total: number;
|
|
375
|
+
perPage: number;
|
|
376
|
+
}
|
|
377
|
+
export const Pagination = createVar<PaginationData>();
|
|
209
378
|
|
|
210
|
-
//
|
|
211
|
-
|
|
379
|
+
// Non-cacheable var — reading inside cache() or "use cache" throws at runtime
|
|
380
|
+
const Session = createVar<SessionData>({ cache: false });
|
|
381
|
+
```
|
|
212
382
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
383
|
+
`createVar` accepts an optional options object. The `cache` option (default
|
|
384
|
+
`true`) controls whether the var's values can be read inside cache scopes.
|
|
385
|
+
Write-level escalation is also supported: `ctx.set(Var, value, { cache: false })`
|
|
386
|
+
marks a specific write as non-cacheable even if the var itself is cacheable.
|
|
387
|
+
"Least cacheable wins" — if either says `cache: false`, the value throws on
|
|
388
|
+
read inside `cache()` or `"use cache"`.
|
|
218
389
|
|
|
219
|
-
|
|
390
|
+
### Producer (handler or middleware)
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { Pagination } from "../vars/pagination.js";
|
|
394
|
+
|
|
395
|
+
const ArticleList: Handler<"articles.list"> = async (ctx) => {
|
|
396
|
+
ctx.set(Pagination, { // type-checked
|
|
397
|
+
current: 1,
|
|
398
|
+
total: 10,
|
|
399
|
+
perPage: 5,
|
|
400
|
+
});
|
|
401
|
+
return <Articles />;
|
|
402
|
+
};
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Consumer (layout, parallel, or any context with get)
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { Pagination } from "../vars/pagination.js";
|
|
409
|
+
|
|
410
|
+
export function PaginationLayout(ctx: any) {
|
|
411
|
+
const pagination = ctx.get(Pagination); // typed as PaginationData | undefined
|
|
412
|
+
if (!pagination) return <Outlet />;
|
|
413
|
+
return <nav>Page {pagination.current} of {pagination.total}</nav>;
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Why not just use RSCRouter.Vars?
|
|
418
|
+
|
|
419
|
+
`RSCRouter.Vars` (via module augmentation) provides app-global typing for
|
|
420
|
+
`ctx.get("key")` / `ctx.set("key", value)`. It works for middleware state
|
|
421
|
+
shared app-wide. `createVar<T>()` is for route-local or feature-scoped
|
|
422
|
+
context -- the producer and consumer import the same token, creating a
|
|
423
|
+
scoped contract without polluting global types.
|
|
424
|
+
|
|
425
|
+
Both approaches coexist: `ctx.get("user")` (global via Vars) and
|
|
426
|
+
`ctx.get(Pagination)` (scoped via createVar) work side by side.
|
|
427
|
+
|
|
428
|
+
## Handle Type Safety
|
|
429
|
+
|
|
430
|
+
Handles have typed data:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// Built-in Breadcrumbs handle — import from "@rangojs/router"
|
|
434
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
435
|
+
// Type: Handle<BreadcrumbItem, BreadcrumbItem[]>
|
|
436
|
+
// BreadcrumbItem: { label: string; href: string; content?: ReactNode | Promise<ReactNode> }
|
|
437
|
+
|
|
438
|
+
// In route handler — push is fully typed
|
|
439
|
+
path("/shop/product/:slug", (ctx) => {
|
|
440
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
441
|
+
breadcrumb({ label: "Products", href: "/shop/products" });
|
|
442
|
+
return <ProductPage />;
|
|
443
|
+
}, { name: "product" });
|
|
444
|
+
|
|
445
|
+
// In client — typed array
|
|
446
|
+
import { useHandle, Breadcrumbs } from "@rangojs/router/client";
|
|
220
447
|
function BreadcrumbNav() {
|
|
221
448
|
const crumbs = useHandle(Breadcrumbs);
|
|
222
|
-
// crumbs:
|
|
449
|
+
// crumbs: BreadcrumbItem[]
|
|
223
450
|
}
|
|
451
|
+
|
|
452
|
+
// Custom handles also work the same way
|
|
453
|
+
import { createHandle } from "@rangojs/router";
|
|
454
|
+
export const PageTitle = createHandle<string, string>(
|
|
455
|
+
(segments) => segments.flat().at(-1) ?? "Default Title"
|
|
456
|
+
);
|
|
224
457
|
```
|
|
225
458
|
|
|
226
459
|
## Ref Prop Type Safety (Loaders & Handles)
|
|
@@ -234,24 +467,24 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
234
467
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
235
468
|
});
|
|
236
469
|
|
|
237
|
-
//
|
|
238
|
-
|
|
470
|
+
// Built-in Breadcrumbs — or any custom handle created with createHandle()
|
|
471
|
+
```
|
|
239
472
|
|
|
473
|
+
```tsx
|
|
240
474
|
// Client component — typeof infers all generics
|
|
241
475
|
"use client";
|
|
242
|
-
import { useLoader, useHandle } from "@rangojs/router/client";
|
|
476
|
+
import { useLoader, useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
243
477
|
import type { ProductLoader } from "../loaders";
|
|
244
|
-
import type { Breadcrumbs } from "../handles";
|
|
245
478
|
|
|
246
479
|
function MyComponent({
|
|
247
480
|
loader,
|
|
248
481
|
handle,
|
|
249
482
|
}: {
|
|
250
|
-
loader: typeof ProductLoader;
|
|
251
|
-
handle: typeof Breadcrumbs;
|
|
483
|
+
loader: typeof ProductLoader; // LoaderDefinition<{ product: Product }>
|
|
484
|
+
handle: typeof Breadcrumbs; // Handle<{ label: string; href: string }>
|
|
252
485
|
}) {
|
|
253
|
-
const { data } = useLoader(loader);
|
|
254
|
-
const crumbs = useHandle(handle);
|
|
486
|
+
const { data } = useLoader(loader); // data is typed
|
|
487
|
+
const crumbs = useHandle(handle); // crumbs is typed array
|
|
255
488
|
// ...
|
|
256
489
|
}
|
|
257
490
|
```
|
|
@@ -266,6 +499,7 @@ full functionality from module-level registries.
|
|
|
266
499
|
// location-states.ts
|
|
267
500
|
import { createLocationState } from "@rangojs/router";
|
|
268
501
|
|
|
502
|
+
// All export patterns work: export const, const + export { X }, export { X as Y }
|
|
269
503
|
export const ProductPreview = createLocationState<{
|
|
270
504
|
name: string;
|
|
271
505
|
price: number;
|
|
@@ -312,8 +546,8 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
312
546
|
"skipLibCheck": true,
|
|
313
547
|
"isolatedModules": true,
|
|
314
548
|
"esModuleInterop": true,
|
|
315
|
-
"resolveJsonModule": true
|
|
316
|
-
}
|
|
549
|
+
"resolveJsonModule": true,
|
|
550
|
+
},
|
|
317
551
|
}
|
|
318
552
|
```
|
|
319
553
|
|
|
@@ -322,7 +556,7 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
322
556
|
{
|
|
323
557
|
"extends": "../../tsconfig.base.json",
|
|
324
558
|
"include": ["src"],
|
|
325
|
-
"files": ["src/router.tsx"]
|
|
559
|
+
"files": ["src/router.tsx"],
|
|
326
560
|
}
|
|
327
561
|
```
|
|
328
562
|
|
|
@@ -331,19 +565,27 @@ global type declarations (like `RSCRouter.Env`).
|
|
|
331
565
|
{
|
|
332
566
|
"extends": "../../tsconfig.base.json",
|
|
333
567
|
"include": ["src"],
|
|
334
|
-
"files": ["src/router.tsx"]
|
|
568
|
+
"files": ["src/router.tsx"],
|
|
335
569
|
}
|
|
336
570
|
```
|
|
337
571
|
|
|
338
|
-
The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter {
|
|
339
|
-
is always included in the compilation even if nothing directly imports it.
|
|
340
|
-
|
|
572
|
+
The `files` array ensures `router.tsx` (which contains `declare global { namespace RSCRouter { interface Env; interface Vars } }`)
|
|
573
|
+
is always included in the compilation even if nothing directly imports it. Route types come from the
|
|
574
|
+
auto-generated `*.named-routes.gen.ts` file (via `rango generate`), not from manual declaration.
|
|
575
|
+
Each app gets its own typed environment without interfering with other apps.
|
|
341
576
|
|
|
342
577
|
## Complete Type-Safe Setup
|
|
343
578
|
|
|
344
579
|
```typescript
|
|
345
580
|
// 1. env.ts - Environment types
|
|
346
|
-
export
|
|
581
|
+
export interface AppBindings {
|
|
582
|
+
DB: D1Database;
|
|
583
|
+
KV: KVNamespace;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export interface AppVariables {
|
|
587
|
+
user?: { id: string; email: string; role: string };
|
|
588
|
+
}
|
|
347
589
|
|
|
348
590
|
// 2. urls.tsx - Route definitions with names
|
|
349
591
|
import { urls } from "@rangojs/router";
|
|
@@ -359,35 +601,40 @@ export const urlpatterns = urls(({ path, layout, loader }) => [
|
|
|
359
601
|
]),
|
|
360
602
|
]);
|
|
361
603
|
|
|
362
|
-
// 3. router.tsx -
|
|
363
|
-
const router = createRouter<
|
|
604
|
+
// 3. router.tsx - Create router and export reverse
|
|
605
|
+
const router = createRouter<AppBindings>({
|
|
364
606
|
document: Document,
|
|
365
|
-
|
|
366
|
-
});
|
|
607
|
+
}).routes(urlpatterns);
|
|
367
608
|
|
|
609
|
+
// Register bindings and variables globally for implicit typing
|
|
368
610
|
declare global {
|
|
369
611
|
namespace RSCRouter {
|
|
370
|
-
interface Env extends
|
|
612
|
+
interface Env extends AppBindings {}
|
|
613
|
+
interface Vars extends AppVariables {}
|
|
371
614
|
}
|
|
372
615
|
}
|
|
373
616
|
|
|
617
|
+
export const reverse = router.reverse;
|
|
374
618
|
export default router;
|
|
375
619
|
|
|
376
|
-
// 4.
|
|
377
|
-
|
|
620
|
+
// 4. Run `npx rango generate src/router.tsx` to generate
|
|
621
|
+
// router.named-routes.gen.ts (auto-registers GeneratedRouteMap globally).
|
|
622
|
+
// No manual RegisteredRoutes declaration needed.
|
|
623
|
+
|
|
624
|
+
// 5. loaders/*.ts - Type-safe loaders
|
|
625
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
378
626
|
// ctx.params: { slug: string }
|
|
379
|
-
// ctx.
|
|
380
|
-
// ctx.env.
|
|
627
|
+
// ctx.get("user"): User | undefined (from RSCRouter.Vars)
|
|
628
|
+
// ctx.env.DB: D1Database (plain bindings from RSCRouter.Env)
|
|
381
629
|
return { product: await fetchProduct(ctx.params.slug) };
|
|
382
630
|
});
|
|
383
631
|
|
|
384
|
-
//
|
|
632
|
+
// 6. Server: ctx.reverse for named routes
|
|
385
633
|
path("/product/:slug", (ctx) => {
|
|
386
|
-
|
|
387
|
-
return <Link to={reverse("shop")}>Back to Shop</Link>;
|
|
634
|
+
return <Link to={ctx.reverse("shop")}>Back to Shop</Link>;
|
|
388
635
|
}, { name: "product" })
|
|
389
636
|
|
|
390
|
-
//
|
|
637
|
+
// 7. Client: useHref for mounted paths, href for absolute
|
|
391
638
|
"use client";
|
|
392
639
|
import { useHref, href, Link } from "@rangojs/router/client";
|
|
393
640
|
<Link to={href("/shop/product/widget")}>Widget</Link>
|