@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81
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 +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +5091 -941
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +61 -52
- package/skills/breadcrumbs/SKILL.md +250 -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 +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +340 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -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/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- 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 +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +91 -11
- package/src/browser/react/context.ts +11 -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 +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 -126
- package/src/browser/react/use-href.tsx +2 -2
- 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 +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -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 +214 -58
- 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 +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -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 +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 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- 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 +3 -1
- package/src/client.tsx +135 -301
- 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 +108 -2
- package/src/handle.ts +55 -29
- 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 +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- 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 +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -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 +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- 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 +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -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 +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- 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 +137 -38
- 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 +748 -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 +1379 -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 +291 -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 +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- 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 +393 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +358 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -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 +134 -36
- package/src/server/context.ts +341 -61
- 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 +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -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 +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- 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 +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -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 +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -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/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} +72 -53
- 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 +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -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 +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 +462 -0
- package/src/vite/router-discovery.ts +977 -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 +221 -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/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- 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/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Node ESM loader hook that resolves `cloudflare:*` imports to the same
|
|
2
|
+
// stub ESM the Vite transform produces for rewritten specifiers.
|
|
3
|
+
//
|
|
4
|
+
// Why both? The Vite transform (cloudflare-protocol-stub.ts) catches
|
|
5
|
+
// imports in modules that flow through Vite's plugin pipeline — covers
|
|
6
|
+
// user source and any node_modules package Vite fetches and transforms.
|
|
7
|
+
// But Vite/Rollup externalize certain packages (e.g. `partyserver`,
|
|
8
|
+
// which has `import { DurableObject, env } from "cloudflare:workers"`
|
|
9
|
+
// at its top level, and similar "workerd-native" libraries). Externalized
|
|
10
|
+
// modules bypass the transform: Rollup hands their resolution to Node's
|
|
11
|
+
// native ESM loader, which rejects URL-scheme specifiers. This loader
|
|
12
|
+
// hook registers via `module.register()` from `createTempRscServer` and
|
|
13
|
+
// intercepts `cloudflare:*` at Node's resolve layer — before the default
|
|
14
|
+
// loader throws ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
15
|
+
//
|
|
16
|
+
// Lifecycle: the hook runs in a dedicated worker thread (Node ESM loader
|
|
17
|
+
// architecture) with its own globalThis. It cannot see the main thread's
|
|
18
|
+
// `__rango_build_env__` bridge, so the `env` export here is always `{}`.
|
|
19
|
+
// That's fine in practice — externalized libraries don't typically touch
|
|
20
|
+
// `env` at module top level; they read it at request time in workerd
|
|
21
|
+
// where the real module exists. Build-time prerender handlers in user
|
|
22
|
+
// source DO read `env`, but they flow through the Vite transform (which
|
|
23
|
+
// does bridge `env` from `getPlatformProxy()`), not through this loader.
|
|
24
|
+
//
|
|
25
|
+
// Keep STUBS in sync with cloudflare-protocol-stub.ts — both paths need
|
|
26
|
+
// to hand out the same base classes.
|
|
27
|
+
|
|
28
|
+
const CF_PREFIX = "cloudflare:";
|
|
29
|
+
|
|
30
|
+
const STUBS = {
|
|
31
|
+
"cloudflare:workers": `
|
|
32
|
+
export class DurableObject { constructor(_ctx, _env) {} }
|
|
33
|
+
export class WorkerEntrypoint { constructor(_ctx, _env) {} }
|
|
34
|
+
export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
|
|
35
|
+
export class RpcTarget {}
|
|
36
|
+
export const env = {};
|
|
37
|
+
export default {};
|
|
38
|
+
`,
|
|
39
|
+
"cloudflare:email": `
|
|
40
|
+
export class EmailMessage { constructor(_from, _to, _raw) {} }
|
|
41
|
+
export default {};
|
|
42
|
+
`,
|
|
43
|
+
"cloudflare:sockets": `
|
|
44
|
+
export function connect() { return {}; }
|
|
45
|
+
export default {};
|
|
46
|
+
`,
|
|
47
|
+
"cloudflare:workflows": `
|
|
48
|
+
export class NonRetryableError extends Error {
|
|
49
|
+
constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
|
|
50
|
+
}
|
|
51
|
+
export default {};
|
|
52
|
+
`,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Policy: unknown `cloudflare:*` specifiers resolve permissively to an
|
|
56
|
+
// empty default export rather than throwing. Same reasoning as
|
|
57
|
+
// cloudflare-protocol-stub.ts's FALLBACK_STUB — we prioritize
|
|
58
|
+
// dependency-graph resilience over strict validation, because third-party
|
|
59
|
+
// packages can pull `cloudflare:*` modules we haven't curated.
|
|
60
|
+
const FALLBACK_STUB = `export default {};\n`;
|
|
61
|
+
|
|
62
|
+
function dataUrlFor(specifier) {
|
|
63
|
+
const body = STUBS[specifier] ?? FALLBACK_STUB;
|
|
64
|
+
return "data:text/javascript;base64," + Buffer.from(body).toString("base64");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
68
|
+
if (specifier.startsWith(CF_PREFIX)) {
|
|
69
|
+
return {
|
|
70
|
+
shortCircuit: true,
|
|
71
|
+
url: dataUrlFor(specifier),
|
|
72
|
+
format: "module",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return nextResolve(specifier, context);
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,34 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.0.0-experimental.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.0.0-experimental.81",
|
|
5
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
6
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"react-server-components",
|
|
8
|
+
"router",
|
|
9
|
+
"rsc",
|
|
10
|
+
"vite"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/ivogt/vite-rsc#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ivogt/vite-rsc/issues"
|
|
15
|
+
},
|
|
7
16
|
"license": "MIT",
|
|
17
|
+
"author": "Ivo Todorov",
|
|
8
18
|
"repository": {
|
|
9
19
|
"type": "git",
|
|
10
20
|
"url": "git+https://github.com/ivogt/vite-rsc.git",
|
|
11
21
|
"directory": "packages/rangojs-router"
|
|
12
22
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"url": "https://github.com/ivogt/vite-rsc/issues"
|
|
23
|
+
"bin": {
|
|
24
|
+
"rango": "./dist/bin/rango.js"
|
|
16
25
|
},
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"!src/**/__tests__",
|
|
29
|
+
"!src/**/__mocks__",
|
|
30
|
+
"!src/**/*.test.ts",
|
|
31
|
+
"!src/**/*.test.tsx",
|
|
32
|
+
"dist",
|
|
33
|
+
"skills",
|
|
34
|
+
"AGENTS.md",
|
|
35
|
+
"README.md"
|
|
27
36
|
],
|
|
37
|
+
"type": "module",
|
|
28
38
|
"exports": {
|
|
29
39
|
".": {
|
|
30
|
-
"react-server": "./src/index.rsc.ts",
|
|
31
40
|
"types": "./src/index.rsc.ts",
|
|
41
|
+
"react-server": "./src/index.rsc.ts",
|
|
32
42
|
"default": "./src/index.ts"
|
|
33
43
|
},
|
|
34
44
|
"./server": {
|
|
@@ -36,8 +46,8 @@
|
|
|
36
46
|
"import": "./src/server.ts"
|
|
37
47
|
},
|
|
38
48
|
"./client": {
|
|
39
|
-
"react-server": "./src/client.rsc.tsx",
|
|
40
49
|
"types": "./src/client.tsx",
|
|
50
|
+
"react-server": "./src/client.rsc.tsx",
|
|
41
51
|
"default": "./src/client.tsx"
|
|
42
52
|
},
|
|
43
53
|
"./browser": {
|
|
@@ -49,8 +59,8 @@
|
|
|
49
59
|
"default": "./src/ssr/index.tsx"
|
|
50
60
|
},
|
|
51
61
|
"./rsc": {
|
|
52
|
-
"react-server": "./src/rsc/index.ts",
|
|
53
62
|
"types": "./src/rsc/index.ts",
|
|
63
|
+
"react-server": "./src/rsc/index.ts",
|
|
54
64
|
"default": "./src/rsc/index.ts"
|
|
55
65
|
},
|
|
56
66
|
"./vite": {
|
|
@@ -58,7 +68,7 @@
|
|
|
58
68
|
"import": "./dist/vite/index.js"
|
|
59
69
|
},
|
|
60
70
|
"./types": {
|
|
61
|
-
"types": "./src/vite/version.d.ts"
|
|
71
|
+
"types": "./src/vite/plugins/version.d.ts"
|
|
62
72
|
},
|
|
63
73
|
"./__internal": {
|
|
64
74
|
"types": "./src/__internal.ts",
|
|
@@ -73,8 +83,8 @@
|
|
|
73
83
|
"default": "./src/deps/ssr.ts"
|
|
74
84
|
},
|
|
75
85
|
"./internal/deps/rsc": {
|
|
76
|
-
"react-server": "./src/deps/rsc.ts",
|
|
77
86
|
"types": "./src/deps/rsc.ts",
|
|
87
|
+
"react-server": "./src/deps/rsc.ts",
|
|
78
88
|
"default": "./src/deps/rsc.ts"
|
|
79
89
|
},
|
|
80
90
|
"./internal/deps/html-stream-client": {
|
|
@@ -86,15 +96,20 @@
|
|
|
86
96
|
"default": "./src/deps/html-stream-server.ts"
|
|
87
97
|
},
|
|
88
98
|
"./internal/rsc-handler": {
|
|
89
|
-
"react-server": "./src/rsc/handler.ts",
|
|
90
99
|
"types": "./src/rsc/handler.ts",
|
|
100
|
+
"react-server": "./src/rsc/handler.ts",
|
|
91
101
|
"default": "./src/rsc/handler.ts"
|
|
92
102
|
},
|
|
93
103
|
"./cache": {
|
|
94
|
-
"react-server": "./src/cache/index.ts",
|
|
95
104
|
"types": "./src/cache/index.ts",
|
|
105
|
+
"react-server": "./src/cache/index.ts",
|
|
96
106
|
"default": "./src/cache/index.ts"
|
|
97
107
|
},
|
|
108
|
+
"./cache-runtime": {
|
|
109
|
+
"types": "./src/cache/cache-runtime.ts",
|
|
110
|
+
"react-server": "./src/cache/cache-runtime.ts",
|
|
111
|
+
"default": "./src/cache/cache-runtime.ts"
|
|
112
|
+
},
|
|
98
113
|
"./theme": {
|
|
99
114
|
"types": "./src/theme/index.ts",
|
|
100
115
|
"default": "./src/theme/index.ts"
|
|
@@ -104,8 +119,8 @@
|
|
|
104
119
|
"import": "./src/build/index.ts"
|
|
105
120
|
},
|
|
106
121
|
"./host": {
|
|
107
|
-
"react-server": "./src/host/index.ts",
|
|
108
122
|
"types": "./src/host/index.ts",
|
|
123
|
+
"react-server": "./src/host/index.ts",
|
|
109
124
|
"default": "./src/host/index.ts"
|
|
110
125
|
},
|
|
111
126
|
"./host/testing": {
|
|
@@ -113,34 +128,14 @@
|
|
|
113
128
|
"default": "./src/host/testing.ts"
|
|
114
129
|
}
|
|
115
130
|
},
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"!src/**/__mocks__",
|
|
120
|
-
"!src/**/*.test.ts",
|
|
121
|
-
"!src/**/*.test.tsx",
|
|
122
|
-
"dist",
|
|
123
|
-
"skills",
|
|
124
|
-
"CLAUDE.md",
|
|
125
|
-
"README.md"
|
|
126
|
-
],
|
|
127
|
-
"peerDependencies": {
|
|
128
|
-
"@cloudflare/vite-plugin": "^1.21.0",
|
|
129
|
-
"@vitejs/plugin-rsc": "^0.5.14",
|
|
130
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
131
|
-
"vite": "^7.3.0"
|
|
132
|
-
},
|
|
133
|
-
"peerDependenciesMeta": {
|
|
134
|
-
"@cloudflare/vite-plugin": {
|
|
135
|
-
"optional": true
|
|
136
|
-
},
|
|
137
|
-
"vite": {
|
|
138
|
-
"optional": true
|
|
139
|
-
}
|
|
131
|
+
"publishConfig": {
|
|
132
|
+
"access": "public",
|
|
133
|
+
"tag": "experimental"
|
|
140
134
|
},
|
|
141
135
|
"dependencies": {
|
|
142
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
136
|
+
"@vitejs/plugin-rsc": "^0.5.23",
|
|
143
137
|
"magic-string": "^0.30.17",
|
|
138
|
+
"picomatch": "^4.0.3",
|
|
144
139
|
"rsc-html-stream": "^0.0.7"
|
|
145
140
|
},
|
|
146
141
|
"devDependencies": {
|
|
@@ -150,14 +145,28 @@
|
|
|
150
145
|
"@types/react-dom": "^19.2.3",
|
|
151
146
|
"esbuild": "^0.27.0",
|
|
152
147
|
"jiti": "^2.6.1",
|
|
153
|
-
"react": "^19.2.
|
|
154
|
-
"react-dom": "^19.2.
|
|
148
|
+
"react": "^19.2.4",
|
|
149
|
+
"react-dom": "^19.2.4",
|
|
155
150
|
"tinyexec": "^0.3.2",
|
|
156
151
|
"typescript": "^5.3.0",
|
|
157
152
|
"vitest": "^4.0.0"
|
|
158
153
|
},
|
|
154
|
+
"peerDependencies": {
|
|
155
|
+
"@cloudflare/vite-plugin": "^1.25.0",
|
|
156
|
+
"@vitejs/plugin-rsc": "^0.5.23",
|
|
157
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
158
|
+
"vite": "^7.3.0"
|
|
159
|
+
},
|
|
160
|
+
"peerDependenciesMeta": {
|
|
161
|
+
"@cloudflare/vite-plugin": {
|
|
162
|
+
"optional": true
|
|
163
|
+
},
|
|
164
|
+
"vite": {
|
|
165
|
+
"optional": true
|
|
166
|
+
}
|
|
167
|
+
},
|
|
159
168
|
"scripts": {
|
|
160
|
-
"build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external",
|
|
169
|
+
"build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
161
170
|
"typecheck": "tsc --noEmit",
|
|
162
171
|
"test": "playwright test",
|
|
163
172
|
"test:ui": "playwright test --ui",
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: breadcrumbs
|
|
3
|
+
description: Built-in Breadcrumbs handle for accumulating breadcrumb navigation across route segments
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Breadcrumbs
|
|
8
|
+
|
|
9
|
+
Built-in handle for accumulating breadcrumb items across route segments.
|
|
10
|
+
Each layout/route pushes items via `ctx.use(Breadcrumbs)`, and they are
|
|
11
|
+
collected in parent-to-child order with automatic deduplication by `href`.
|
|
12
|
+
|
|
13
|
+
## BreadcrumbItem Type
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
interface BreadcrumbItem {
|
|
17
|
+
label: string; // Display text
|
|
18
|
+
href: string; // URL the breadcrumb links to
|
|
19
|
+
content?: ReactNode | Promise<ReactNode>; // Optional extra content (sync or async)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Pushing Breadcrumbs (Server)
|
|
24
|
+
|
|
25
|
+
Import `Breadcrumbs` from `@rangojs/router` in RSC/server context:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { urls, Breadcrumbs } from "@rangojs/router";
|
|
29
|
+
import { Outlet } from "@rangojs/router/client";
|
|
30
|
+
|
|
31
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
32
|
+
// Root layout pushes "Home"
|
|
33
|
+
layout((ctx) => {
|
|
34
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
35
|
+
breadcrumb({ label: "Home", href: "/" });
|
|
36
|
+
return <RootLayout />;
|
|
37
|
+
}, () => [
|
|
38
|
+
path("/", HomePage, { name: "home" }),
|
|
39
|
+
|
|
40
|
+
// Nested layout pushes "Blog"
|
|
41
|
+
layout((ctx) => {
|
|
42
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
43
|
+
breadcrumb({ label: "Blog", href: "/blog" });
|
|
44
|
+
return <BlogLayout />;
|
|
45
|
+
}, () => [
|
|
46
|
+
path("/blog", BlogIndex, { name: "blog.index" }),
|
|
47
|
+
|
|
48
|
+
// Route handler pushes post title
|
|
49
|
+
path("/blog/:slug", (ctx) => {
|
|
50
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
51
|
+
breadcrumb({ label: ctx.params.slug, href: `/blog/${ctx.params.slug}` });
|
|
52
|
+
return <BlogPost slug={ctx.params.slug} />;
|
|
53
|
+
}, { name: "blog.post" }),
|
|
54
|
+
]),
|
|
55
|
+
]),
|
|
56
|
+
]);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On `/blog/my-post`, breadcrumbs accumulate: `Home > Blog > my-post`.
|
|
60
|
+
|
|
61
|
+
## Async Content
|
|
62
|
+
|
|
63
|
+
The `content` field supports `Promise<ReactNode>` for streaming:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
path("/product/:id", async (ctx) => {
|
|
67
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
68
|
+
const productPromise = fetchProduct(ctx.params.id);
|
|
69
|
+
|
|
70
|
+
breadcrumb({
|
|
71
|
+
label: "Product",
|
|
72
|
+
href: `/product/${ctx.params.id}`,
|
|
73
|
+
content: productPromise.then((p) => <span>({p.category})</span>),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const product = await productPromise;
|
|
77
|
+
return <ProductPage product={product} />;
|
|
78
|
+
}, { name: "product" })
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Async content is a `Promise<ReactNode>`. Resolve it in your component
|
|
82
|
+
with React's `use()` hook wrapped in `<Suspense>`.
|
|
83
|
+
|
|
84
|
+
## Consuming Breadcrumbs (Client)
|
|
85
|
+
|
|
86
|
+
Use `useHandle(Breadcrumbs)` in a client component to read the accumulated items:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
"use client";
|
|
90
|
+
import { useHandle, Breadcrumbs, Link } from "@rangojs/router/client";
|
|
91
|
+
|
|
92
|
+
function BreadcrumbNav() {
|
|
93
|
+
const breadcrumbs = useHandle(Breadcrumbs);
|
|
94
|
+
|
|
95
|
+
if (!breadcrumbs.length) return null;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<nav aria-label="Breadcrumb">
|
|
99
|
+
<ol>
|
|
100
|
+
{breadcrumbs.map((crumb, i) => (
|
|
101
|
+
<li key={crumb.href}>
|
|
102
|
+
{i === breadcrumbs.length - 1 ? (
|
|
103
|
+
<span aria-current="page">{crumb.label}</span>
|
|
104
|
+
) : (
|
|
105
|
+
<Link to={crumb.href}>{crumb.label}</Link>
|
|
106
|
+
)}
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
</ol>
|
|
110
|
+
</nav>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### With Selector
|
|
116
|
+
|
|
117
|
+
Re-render only when the selected value changes:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Only the last breadcrumb
|
|
121
|
+
const current = useHandle(Breadcrumbs, (data) => data.at(-1));
|
|
122
|
+
|
|
123
|
+
// Breadcrumb count
|
|
124
|
+
const count = useHandle(Breadcrumbs, (data) => data.length);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Deduplication
|
|
128
|
+
|
|
129
|
+
The built-in collect function deduplicates by `href`. If multiple segments
|
|
130
|
+
push the same `href`, the last one wins. This prevents duplicates when
|
|
131
|
+
navigating between sibling routes that share a common breadcrumb.
|
|
132
|
+
|
|
133
|
+
## Passing as Props
|
|
134
|
+
|
|
135
|
+
Breadcrumbs handle can be passed from server to client components:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// Server component
|
|
139
|
+
path("/dashboard", (ctx) => {
|
|
140
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
141
|
+
breadcrumb({ label: "Dashboard", href: "/dashboard" });
|
|
142
|
+
return <DashboardNav handle={Breadcrumbs} />;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Client component
|
|
146
|
+
("use client");
|
|
147
|
+
import { useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
148
|
+
|
|
149
|
+
function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
|
|
150
|
+
const crumbs = useHandle(handle);
|
|
151
|
+
return (
|
|
152
|
+
<nav>
|
|
153
|
+
{crumbs.map((c) => (
|
|
154
|
+
<a href={c.href}>{c.label}</a>
|
|
155
|
+
))}
|
|
156
|
+
</nav>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Complete Example
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// urls.tsx
|
|
165
|
+
import { urls, Breadcrumbs, Meta } from "@rangojs/router";
|
|
166
|
+
import { Outlet, MetaTags } from "@rangojs/router/client";
|
|
167
|
+
import { BreadcrumbNav } from "./components/BreadcrumbNav";
|
|
168
|
+
|
|
169
|
+
function RootLayout() {
|
|
170
|
+
return (
|
|
171
|
+
<html lang="en">
|
|
172
|
+
<head><MetaTags /></head>
|
|
173
|
+
<body>
|
|
174
|
+
<BreadcrumbNav />
|
|
175
|
+
<main><Outlet /></main>
|
|
176
|
+
</body>
|
|
177
|
+
</html>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
182
|
+
layout((ctx) => {
|
|
183
|
+
ctx.use(Breadcrumbs)({ label: "Home", href: "/" });
|
|
184
|
+
ctx.use(Meta)({ title: "My App" });
|
|
185
|
+
return <RootLayout />;
|
|
186
|
+
}, () => [
|
|
187
|
+
path("/", () => <h1>Welcome</h1>, { name: "home" }),
|
|
188
|
+
|
|
189
|
+
layout((ctx) => {
|
|
190
|
+
ctx.use(Breadcrumbs)({ label: "Shop", href: "/shop" });
|
|
191
|
+
return <Outlet />;
|
|
192
|
+
}, () => [
|
|
193
|
+
path("/shop", () => <h1>Shop</h1>, { name: "shop" }),
|
|
194
|
+
path("/shop/:slug", (ctx) => {
|
|
195
|
+
ctx.use(Breadcrumbs)({
|
|
196
|
+
label: ctx.params.slug,
|
|
197
|
+
href: `/shop/${ctx.params.slug}`,
|
|
198
|
+
});
|
|
199
|
+
return <h1>Product: {ctx.params.slug}</h1>;
|
|
200
|
+
}, { name: "shop.product" }),
|
|
201
|
+
]),
|
|
202
|
+
]),
|
|
203
|
+
]);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Navigating to `/shop/widget` produces: `Home / Shop / widget`
|
|
207
|
+
|
|
208
|
+
## Custom Handles
|
|
209
|
+
|
|
210
|
+
Create your own handle with `createHandle()`:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { createHandle } from "@rangojs/router";
|
|
214
|
+
|
|
215
|
+
// Default: flatten into array
|
|
216
|
+
export const PageTitle = createHandle<string, string>(
|
|
217
|
+
(segments) => segments.flat().at(-1) ?? "Default Title",
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// No collect function: default flattens into T[]
|
|
221
|
+
export const Warnings = createHandle<string>();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The Vite `exposeInternalIds` plugin auto-injects a stable `$$id` based on
|
|
225
|
+
file path and export name. No manual naming required for project-local code.
|
|
226
|
+
|
|
227
|
+
### Handles in 3rd-party packages
|
|
228
|
+
|
|
229
|
+
The `exposeInternalIds` plugin skips `node_modules/`, so handles defined in
|
|
230
|
+
published packages won't get auto-injected IDs. Pass a manual tag as the
|
|
231
|
+
second argument to `createHandle()`:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { createHandle } from "@rangojs/router";
|
|
235
|
+
|
|
236
|
+
// With a collect function (reducer): collect is first arg, tag is second
|
|
237
|
+
export const Breadcrumbs = createHandle<BreadcrumbItem, BreadcrumbItem[]>(
|
|
238
|
+
collectBreadcrumbs,
|
|
239
|
+
"__my_package_breadcrumbs__",
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Without a collect function: pass undefined, then the tag
|
|
243
|
+
export const Warnings = createHandle<string>(
|
|
244
|
+
undefined,
|
|
245
|
+
"__my_package_warnings__",
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The tag must be globally unique and stable across builds. Without it,
|
|
250
|
+
`createHandle` throws in development mode.
|