@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d
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/README.md +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2154 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +84 -11
- package/src/browser/navigation-client.ts +104 -68
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +183 -44
- package/src/browser/prefetch/fetch.ts +228 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +32 -1
- package/src/browser/rsc-router.tsx +69 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +95 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +54 -17
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +116 -19
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +52 -30
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +57 -61
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +67 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +285 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +106 -75
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-compiler
|
|
3
|
+
description: Enable the React Compiler in a Rango app the @vitejs/plugin-rsc way — a separate @rolldown/plugin-babel running reactCompilerPreset(), ordered after react() and before the plugin that supplies @vitejs/plugin-rsc. Use when a consumer wants to turn React Compiler on, hits the dead plugin-react v6 `react({ babel })` path, or is unsure why server components aren't being compiled.
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React Compiler
|
|
8
|
+
|
|
9
|
+
React Compiler is **opt-in** in Rango. The plugin pipeline is fully compatible —
|
|
10
|
+
you just add one more plugin. The catch on a current Rango stack (Vite 8 +
|
|
11
|
+
`@vitejs/plugin-react` v6) is that **v6 dropped its internal Babel for oxc**, so
|
|
12
|
+
the way the React docs and most blog posts show it — `react({ babel: { plugins:
|
|
13
|
+
[...] } })` — silently does nothing. The compiler has to be its own top-level
|
|
14
|
+
plugin.
|
|
15
|
+
|
|
16
|
+
## The shape (read first)
|
|
17
|
+
|
|
18
|
+
- The compiler is a **Babel** plugin, run via
|
|
19
|
+
[`@rolldown/plugin-babel`](https://www.npmjs.com/package/@rolldown/plugin-babel)
|
|
20
|
+
with `reactCompilerPreset()` from `@vitejs/plugin-react`.
|
|
21
|
+
- **Ordering is load-bearing:** put `babel(...)` **after `react()`** and
|
|
22
|
+
**before the plugin that supplies `@vitejs/plugin-rsc`**. In a default Rango
|
|
23
|
+
app that plugin is `rango()` itself; in a Cloudflare app it is
|
|
24
|
+
`@cloudflare/vite-plugin`.
|
|
25
|
+
- **It is client-only.** `reactCompilerPreset()` gates itself to the client
|
|
26
|
+
environment. Server/RSC components are not compiled, and that is the upstream
|
|
27
|
+
example's behavior — not a Rango limitation. See
|
|
28
|
+
[What gets compiled](#what-gets-compiled-client-only).
|
|
29
|
+
- **Rango's build-time prerender is unaffected.** You do not need to do anything
|
|
30
|
+
special. See [Prerender](#interaction-with-build-time-prerender).
|
|
31
|
+
|
|
32
|
+
## Step 1: Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler
|
|
36
|
+
# TypeScript users also want the Babel core types:
|
|
37
|
+
pnpm add -D @types/babel__core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
React 19 ships `react/compiler-runtime` in-tree, so there is **no** extra runtime
|
|
41
|
+
to install and **no** `target` option to set. Only pass `target: '17' | '18'` to
|
|
42
|
+
`reactCompilerPreset()` if you are on an older React.
|
|
43
|
+
|
|
44
|
+
## Step 2: Wire it in
|
|
45
|
+
|
|
46
|
+
### Default (non-Cloudflare) app
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// vite.config.ts
|
|
50
|
+
import { defineConfig } from "vite";
|
|
51
|
+
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
52
|
+
import babel from "@rolldown/plugin-babel";
|
|
53
|
+
import { rango } from "@rangojs/router/vite";
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
react(),
|
|
58
|
+
babel({ presets: [reactCompilerPreset()] }),
|
|
59
|
+
rango(), // supplies @vitejs/plugin-rsc
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Cloudflare app
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// vite.config.ts
|
|
68
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
69
|
+
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
70
|
+
import babel from "@rolldown/plugin-babel";
|
|
71
|
+
import { defineConfig } from "vite";
|
|
72
|
+
import { rango } from "@rangojs/router/vite";
|
|
73
|
+
|
|
74
|
+
export default defineConfig({
|
|
75
|
+
plugins: [
|
|
76
|
+
react(),
|
|
77
|
+
babel({ presets: [reactCompilerPreset()] }),
|
|
78
|
+
rango({ preset: "cloudflare" }),
|
|
79
|
+
cloudflare({
|
|
80
|
+
/* ... */
|
|
81
|
+
}), // supplies @vitejs/plugin-rsc
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## What gets compiled (client-only)
|
|
87
|
+
|
|
88
|
+
`reactCompilerPreset()` carries
|
|
89
|
+
`rolldown.applyToEnvironmentHook: (env) => env.config.consumer === "client"`, so
|
|
90
|
+
even though the babel plugin is top-level, the transform runs **only in the
|
|
91
|
+
`client` environment**:
|
|
92
|
+
|
|
93
|
+
| Environment | `consumer` | Compiled? |
|
|
94
|
+
| ----------- | ---------- | --------- |
|
|
95
|
+
| client | `client` | Yes |
|
|
96
|
+
| ssr | `server` | No |
|
|
97
|
+
| rsc | `server` | No |
|
|
98
|
+
|
|
99
|
+
This matches the upstream `@vitejs/plugin-rsc` example. If you genuinely need to
|
|
100
|
+
compile **server** components, you would have to invoke
|
|
101
|
+
`babel-plugin-react-compiler` yourself without the preset's
|
|
102
|
+
`applyToEnvironmentHook` — that is outside what the example does and is not
|
|
103
|
+
covered here.
|
|
104
|
+
|
|
105
|
+
## Options
|
|
106
|
+
|
|
107
|
+
`reactCompilerPreset()` forwards to `babel-plugin-react-compiler`:
|
|
108
|
+
|
|
109
|
+
| Option | Effect |
|
|
110
|
+
| ------------------------------- | -------------------------------------------------------------------------------------- |
|
|
111
|
+
| `compilationMode: 'annotation'` | Compile only components marked with the `"use memo"` directive, not every eligible one |
|
|
112
|
+
| `target: '17' \| '18'` | Emit `react-compiler-runtime` calls for React < 19. Omit on React 19+. |
|
|
113
|
+
|
|
114
|
+
## Interaction with build-time prerender
|
|
115
|
+
|
|
116
|
+
Nothing to configure. Rango's discovery/prerender step runs a throwaway temp Vite
|
|
117
|
+
server (`createTempRscServer`) that forwards only your **resolution** plugins
|
|
118
|
+
(`resolveId` / `load`). A pure transform plugin like `@rolldown/plugin-babel` is
|
|
119
|
+
intentionally **not** forwarded — and that is correct: the temp runner only
|
|
120
|
+
produces **data** (serialized Flight payloads + the route manifest), not shipped
|
|
121
|
+
code, and React Compiler is a memoization-only transform that does not change
|
|
122
|
+
rendered output. Your shipped client bundle still gets compiled, because the
|
|
123
|
+
babel plugin lives in your app's top-level plugin array alongside `react()`.
|
|
124
|
+
|
|
125
|
+
## Step 3: Verify the compiler actually ran
|
|
126
|
+
|
|
127
|
+
A compiled module imports the cache allocator from `react/compiler-runtime` and
|
|
128
|
+
calls `_c(n)`. Those two appear in **every** compiled module, so they are the
|
|
129
|
+
reliable per-module signal in dev:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pnpm dev
|
|
133
|
+
# fetch any client component module straight from Vite and look for the markers:
|
|
134
|
+
curl -s "http://localhost:5173/src/components/SomeClientComponent.tsx" \
|
|
135
|
+
| grep -E "compiler-runtime|_c\("
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For a production build, grep the built client bundle for the compiler's
|
|
139
|
+
input-independent cache check, which has a **zero baseline** without the compiler:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm build
|
|
143
|
+
grep -r "Symbol.for(\"react.memo_cache_sentinel\")" dist/client/assets/ | head
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Note the **comparison** form `$[i] === Symbol.for("react.memo_cache_sentinel")`
|
|
147
|
+
is only emitted for components with input-independent JSX, so it is reliable over
|
|
148
|
+
the **whole** client bundle, not necessarily in one chosen module. (React core
|
|
149
|
+
also defines that symbol once with a single `=` assignment, so count comparisons,
|
|
150
|
+
not the bare string.) Run the same grep over `dist/rsc` / `dist/ssr` and you
|
|
151
|
+
should find **none** — that is the client-only contract.
|
|
152
|
+
|
|
153
|
+
## Troubleshooting
|
|
154
|
+
|
|
155
|
+
| Symptom | Cause / fix |
|
|
156
|
+
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| Nothing is compiled; no `compiler-runtime` import anywhere | You used `react({ babel: { plugins: [...] } })`. plugin-react v6 has no internal Babel — add `@rolldown/plugin-babel` as its own plugin. |
|
|
158
|
+
| Client compiled, but server/RSC components are not | Expected. `reactCompilerPreset()` is client-only (see the table). Not a bug. |
|
|
159
|
+
| `Cannot find module 'babel-plugin-react-compiler'` (or `@babel/core`) | Install the peer deps from Step 1; they are not bundled by `reactCompilerPreset()`. |
|
|
160
|
+
| Build pulls in `react-compiler-runtime` | You set `target: '17'`/`'18'` on React 19. Drop `target` — React 19 ships `react/compiler-runtime` in-tree. |
|
|
161
|
+
| Output looks compiled but a component misbehaves | The component likely breaks the Rules of React. Fix the component, or scope the compiler with `compilationMode: 'annotation'` while you do. |
|
|
162
|
+
|
|
163
|
+
## Reference
|
|
164
|
+
|
|
165
|
+
A worked, tested wiring (dev + production e2e markers, incl. the client-only
|
|
166
|
+
contract) lives in the `@rangojs/router` repo: `docs/react-compiler.md` and the
|
|
167
|
+
`react-compiler.test.ts` files under `e2e/e2e-basic`, `tests/cloudflare-basic`,
|
|
168
|
+
and `tests/vite-rsc-demo`.
|
|
@@ -68,16 +68,16 @@ export const urlpatterns = urls(({ path, layout, include }) => [
|
|
|
68
68
|
|
|
69
69
|
## Available Tags
|
|
70
70
|
|
|
71
|
-
| Tag | Usage | Handler returns | Auto-wrap
|
|
72
|
-
| -------- | --------------- | ------------------ |
|
|
73
|
-
| `json` | `path.json()` | plain object/array |
|
|
74
|
-
| `text` | `path.text()` | string | text/plain Response
|
|
75
|
-
| `html` | `path.html()` | string | text/html Response
|
|
76
|
-
| `xml` | `path.xml()` | string | application/xml Response
|
|
77
|
-
| `md` | `path.md()` | string | text/markdown Response
|
|
78
|
-
| `image` | `path.image()` | Response | pass-through
|
|
79
|
-
| `stream` | `path.stream()` | Response | pass-through
|
|
80
|
-
| `any` | `path.any()` | Response | pass-through
|
|
71
|
+
| Tag | Usage | Handler returns | Auto-wrap |
|
|
72
|
+
| -------- | --------------- | ------------------ | ----------------------------- |
|
|
73
|
+
| `json` | `path.json()` | plain object/array | bare JSON value (no envelope) |
|
|
74
|
+
| `text` | `path.text()` | string | text/plain Response |
|
|
75
|
+
| `html` | `path.html()` | string | text/html Response |
|
|
76
|
+
| `xml` | `path.xml()` | string | application/xml Response |
|
|
77
|
+
| `md` | `path.md()` | string | text/markdown Response |
|
|
78
|
+
| `image` | `path.image()` | Response | pass-through |
|
|
79
|
+
| `stream` | `path.stream()` | Response | pass-through |
|
|
80
|
+
| `any` | `path.any()` | Response | pass-through |
|
|
81
81
|
|
|
82
82
|
## ResponseHandlerContext
|
|
83
83
|
|
|
@@ -139,22 +139,31 @@ path.json(
|
|
|
139
139
|
);
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
## JSON
|
|
142
|
+
## JSON Wire Shape
|
|
143
143
|
|
|
144
|
-
`path.json()` handlers return plain data. The framework
|
|
145
|
-
|
|
144
|
+
`path.json()` handlers return plain data. The framework serializes the handler's
|
|
145
|
+
return value **verbatim** (no envelope) on success, and an RFC 9457 `problem+json`
|
|
146
|
+
body on error. Discriminate with `res.ok` / the HTTP status — there is no in-body
|
|
147
|
+
`data`/`error` union:
|
|
146
148
|
|
|
147
149
|
```typescript
|
|
148
|
-
// Success: HTTP 200
|
|
149
|
-
{ "
|
|
150
|
-
|
|
151
|
-
// Error: HTTP 404 (or whatever status RouterError specifies)
|
|
152
|
-
|
|
150
|
+
// Success: HTTP 200, content-type application/json
|
|
151
|
+
{ "status": "ok", "timestamp": 1700000000 }
|
|
152
|
+
|
|
153
|
+
// Error: HTTP 404 (or whatever status RouterError specifies),
|
|
154
|
+
// content-type application/problem+json
|
|
155
|
+
{
|
|
156
|
+
"title": "Not Found",
|
|
157
|
+
"status": 404,
|
|
158
|
+
"detail": "Product 999 not found",
|
|
159
|
+
"code": "NOT_FOUND"
|
|
160
|
+
// "stack": included in development only
|
|
161
|
+
}
|
|
153
162
|
```
|
|
154
163
|
|
|
155
164
|
### Error Handling with RouterError
|
|
156
165
|
|
|
157
|
-
Throw `RouterError` to return structured
|
|
166
|
+
Throw `RouterError` to return a structured `problem+json` body:
|
|
158
167
|
|
|
159
168
|
```typescript
|
|
160
169
|
import { RouterError } from "@rangojs/router";
|
|
@@ -199,25 +208,27 @@ path.json(
|
|
|
199
208
|
|
|
200
209
|
## Client-Side Type Safety
|
|
201
210
|
|
|
202
|
-
###
|
|
211
|
+
### Discriminating success vs. error with res.ok
|
|
212
|
+
|
|
213
|
+
Success bodies are the bare value; error bodies are RFC 9457 `ProblemDetails`.
|
|
214
|
+
Branch on `res.ok` (or the HTTP status) — not an in-body union:
|
|
203
215
|
|
|
204
216
|
```typescript
|
|
205
217
|
"use client";
|
|
206
|
-
import type {
|
|
207
|
-
import { isResponseError } from "@rangojs/router/client";
|
|
218
|
+
import type { ProblemDetails } from "@rangojs/router";
|
|
208
219
|
|
|
209
220
|
// Fetch a typed response
|
|
210
221
|
const res = await fetch("/api/products/1");
|
|
211
|
-
const result: ResponseEnvelope<Product> = await res.json();
|
|
212
222
|
|
|
213
|
-
if (
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
// Error body: application/problem+json
|
|
225
|
+
const problem: ProblemDetails = await res.json();
|
|
226
|
+
// problem.detail: string, problem.code: string, problem.status: number
|
|
227
|
+
console.error(problem.code, problem.detail);
|
|
217
228
|
} else {
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
console.log(
|
|
229
|
+
// Success body: the bare value (no envelope)
|
|
230
|
+
const product: Product = await res.json();
|
|
231
|
+
console.log(product.name);
|
|
221
232
|
}
|
|
222
233
|
```
|
|
223
234
|
|
|
@@ -230,28 +241,78 @@ import type { RouteResponse } from "@rangojs/router";
|
|
|
230
241
|
|
|
231
242
|
// From the apiPatterns module (before include)
|
|
232
243
|
type HealthData = RouteResponse<typeof apiPatterns, "health">;
|
|
233
|
-
// =
|
|
244
|
+
// = { status: string; timestamp: number }
|
|
234
245
|
|
|
235
246
|
type ProductsData = RouteResponse<typeof apiPatterns, "products">;
|
|
236
|
-
// =
|
|
247
|
+
// = { id: string; name: string; price: number }[]
|
|
237
248
|
```
|
|
238
249
|
|
|
239
|
-
|
|
250
|
+
`RouteResponse` is the bare success payload (the JSON wire shape) — the same value
|
|
251
|
+
a `fetch().then(r => r.json())` yields on a 2xx. Error bodies are `ProblemDetails`,
|
|
252
|
+
keyed off `res.ok` at runtime, not part of this type.
|
|
240
253
|
|
|
241
|
-
|
|
254
|
+
### Rango.PathResponse (global lookup by URL pattern or concrete path)
|
|
255
|
+
|
|
256
|
+
`Rango.PathResponse` is ambient (no import) and reads from `RegisteredRoutes`,
|
|
257
|
+
which carries response payload metadata. That surface is **not** auto-wired —
|
|
258
|
+
without the augmentation below, `Rango.PathResponse` falls back to the generated
|
|
259
|
+
path/search map, or to a permissive map when nothing is generated. Either way, it
|
|
260
|
+
has no response payload metadata, so response routes resolve to `never`:
|
|
242
261
|
|
|
243
262
|
```typescript
|
|
244
|
-
|
|
263
|
+
// router.tsx
|
|
264
|
+
export const router = createRouter({ document: Document }).routes(urlpatterns);
|
|
265
|
+
|
|
266
|
+
declare global {
|
|
267
|
+
namespace Rango {
|
|
268
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
With that in place, look up the response type by URL pattern (ambient, no import):
|
|
245
274
|
|
|
275
|
+
```typescript
|
|
246
276
|
// After include("/api", apiPatterns) in main urls
|
|
247
|
-
type Health = PathResponse<"/api/health">;
|
|
248
|
-
// =
|
|
277
|
+
type Health = Rango.PathResponse<"/api/health">;
|
|
278
|
+
// = { status: string; timestamp: number }
|
|
279
|
+
|
|
280
|
+
// RSC routes (no JSON payload) return never
|
|
281
|
+
type Home = Rango.PathResponse<"/">;
|
|
282
|
+
// = never
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`Rango.PathResponse` also accepts a **concrete path**, so it types a `fetch`
|
|
286
|
+
wrapper whose response is inferred from the path you pass:
|
|
249
287
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
import { href } from "@rangojs/router/client";
|
|
290
|
+
|
|
291
|
+
async function get<T extends Rango.Path>(
|
|
292
|
+
path: T,
|
|
293
|
+
): Promise<Rango.PathResponse<T>> {
|
|
294
|
+
return fetch(href(path)).then((r) => r.json());
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const product = await get("/api/products/42"); // Product (bare value)
|
|
253
298
|
```
|
|
254
299
|
|
|
300
|
+
Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
|
|
301
|
+
route can match several patterns and union their responses.
|
|
302
|
+
|
|
303
|
+
`Rango.PathResponse` reports the JSON **wire** shape, not the handler's raw
|
|
304
|
+
return: `path.json()` serializes with `JSON.stringify`, so a handler returning
|
|
305
|
+
`{ createdAt: Date }` resolves to the bare `{ createdAt: string }`. This
|
|
306
|
+
runs through the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
|
|
307
|
+
honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). The
|
|
308
|
+
`RouteResponse` surface below applies the same `Rango.JsonSerialize` transform, so
|
|
309
|
+
both response lookups report the identical wire shape.
|
|
310
|
+
|
|
311
|
+
For local/scoped response typing without global augmentation, prefer
|
|
312
|
+
`RouteResponse<typeof patterns, "routeName">` (see the section above) — it reads
|
|
313
|
+
the response payload straight from the `urls()` patterns and needs no
|
|
314
|
+
`RegisteredRoutes` wiring.
|
|
315
|
+
|
|
255
316
|
### ParamsFor with Response Routes
|
|
256
317
|
|
|
257
318
|
```typescript
|
|
@@ -361,15 +422,17 @@ export const urlpatterns = urls(({ path, include }) => [
|
|
|
361
422
|
|
|
362
423
|
```typescript
|
|
363
424
|
import type { RouteResponse } from "@rangojs/router";
|
|
364
|
-
import type {
|
|
425
|
+
import type { ParamsFor } from "@rangojs/router/client";
|
|
365
426
|
|
|
366
|
-
// Scoped (before mount) -- use the module directly
|
|
427
|
+
// Scoped (before mount) -- use the module directly, no global wiring needed
|
|
367
428
|
type Stats = RouteResponse<typeof blogApiPatterns, "stats">;
|
|
368
|
-
// =
|
|
429
|
+
// = { views: number; visitors: number }
|
|
369
430
|
|
|
370
|
-
// After mounting -- names get prefixed
|
|
371
|
-
|
|
372
|
-
//
|
|
431
|
+
// After mounting -- names get prefixed.
|
|
432
|
+
// Rango.PathResponse needs `RegisteredRoutes extends typeof router.routeMap` (see above),
|
|
433
|
+
// otherwise it resolves to never.
|
|
434
|
+
type BlogStats = Rango.PathResponse<"/blog/api/stats">;
|
|
435
|
+
// = { views: number; visitors: number }
|
|
373
436
|
|
|
374
437
|
// Params work through nested includes
|
|
375
438
|
type LikesParams = ParamsFor<"blog.api.likes">;
|
|
@@ -400,12 +463,24 @@ path(
|
|
|
400
463
|
Multiple response types can share the same URL pattern. See `/mime-routes` for the
|
|
401
464
|
full content negotiation API (Accept header matching, Vary: Accept, multi-variant routes).
|
|
402
465
|
|
|
466
|
+
## Long-Lived Responses (SSE / WebSocket)
|
|
467
|
+
|
|
468
|
+
For Server-Sent Events (`path.stream`) and WebSocket upgrades (`path.any`
|
|
469
|
+
returning a 101 / `webSocket` Response), see `/streams-and-websockets`.
|
|
470
|
+
Upgrade responses flow through without reconstruction; `Vary` and
|
|
471
|
+
`Server-Timing` are skipped, and stub headers are applied in place on a
|
|
472
|
+
best-effort basis.
|
|
473
|
+
|
|
403
474
|
## How It Works
|
|
404
475
|
|
|
405
476
|
1. `path.json()` tags the route at the trie level with a MIME type
|
|
406
477
|
2. `coreRequestHandler()` checks the tag before the RSC pipeline
|
|
407
478
|
3. Tagged routes short-circuit: handler runs, Response is returned directly
|
|
408
|
-
4. JSON routes
|
|
479
|
+
4. JSON routes serialize the return value verbatim (bare) on success; a thrown error becomes an RFC 9457 `problem+json` body (`application/problem+json`)
|
|
409
480
|
5. Client-side navigation to response routes gets `X-RSC-Reload` header, triggering hard navigation
|
|
410
481
|
6. Response types flow through `_responses` phantom type on `UrlPatterns`, propagated by `include()`
|
|
411
482
|
7. When multiple routes share a URL pattern, the trie merges them for content negotiation (see `/mime-routes`)
|
|
483
|
+
|
|
484
|
+
## Consuming response routes
|
|
485
|
+
|
|
486
|
+
To call your own response-route JSON APIs from first-party TypeScript with a typed client (typed params, typed payloads inferred from the handler, no `.data`, typed `ProblemDetails` errors), see `/api-client` — a copy-paste recipe over `RouteResponse` + `ExtractParams` + a client-safe path builder. External/third-party consumers use the plain wire directly: bare JSON on success, `application/problem+json` on error.
|
package/skills/route/SKILL.md
CHANGED
|
@@ -33,6 +33,26 @@ urls(({ path }) => [
|
|
|
33
33
|
]);
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Optional URL params at runtime
|
|
37
|
+
|
|
38
|
+
Absent optional params are **omitted from `ctx.params`** — `ctx.params.<name>`
|
|
39
|
+
reads as `undefined`, matching the `RouteParams<"name">` type
|
|
40
|
+
(`{ query?: string }`). Use `??` to default and `=== undefined` to check
|
|
41
|
+
absence:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
path("/search/:query?", (ctx) => {
|
|
45
|
+
const query = ctx.params.query ?? ""; // works — undefined coalesces
|
|
46
|
+
if (ctx.params.query === undefined) return <EmptySearch />;
|
|
47
|
+
return <Results query={ctx.params.query} />;
|
|
48
|
+
}, { name: "search" });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For the common pattern of an optional locale prefix
|
|
52
|
+
(`include("/:locale?", routes)`) and the wider react-intl integration —
|
|
53
|
+
locale detection, fallback chains, URL generation with absent locale —
|
|
54
|
+
see `/i18n`.
|
|
55
|
+
|
|
36
56
|
## Route Handler Patterns
|
|
37
57
|
|
|
38
58
|
### Component Function
|
|
@@ -214,14 +234,22 @@ Cacheable vars (the default) can be read freely inside cache scopes.
|
|
|
214
234
|
|
|
215
235
|
### Revalidation Contracts for Handler Data
|
|
216
236
|
|
|
237
|
+
> **Scope: `revalidate()` is a partial-render concern, not a cache concern.**
|
|
238
|
+
> It decides whether this segment re-runs and streams to the client on a
|
|
239
|
+
> navigation or action — never whether a cached value is stale. The cache
|
|
240
|
+
> decides hit/miss/ttl/swr independently and never reads `revalidate()`. See
|
|
241
|
+
> `/cache-guide` → "Two axes" and `/rango` → "The shape of rango".
|
|
242
|
+
|
|
217
243
|
Handler-first guarantees apply within a single full render pass. For partial
|
|
218
244
|
action revalidation, define named revalidation contracts and reuse them on both
|
|
219
245
|
the producer route and the consumer child segments.
|
|
220
246
|
|
|
221
247
|
```typescript
|
|
222
248
|
// revalidation-contracts.ts
|
|
249
|
+
// Defer (|| undefined), not ?? false: a hard `false` short-circuits the chain,
|
|
250
|
+
// so when the same segment composes multiple contracts the later ones never run.
|
|
223
251
|
export const revalidateCheckoutData = ({ actionId }) =>
|
|
224
|
-
actionId?.includes("src/actions/checkout.ts#")
|
|
252
|
+
actionId?.includes("src/actions/checkout.ts#") || undefined;
|
|
225
253
|
|
|
226
254
|
path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
227
255
|
revalidate(revalidateCheckoutData), // producer (route handler) reruns
|
|
@@ -250,9 +278,6 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
|
|
|
250
278
|
]);
|
|
251
279
|
```
|
|
252
280
|
|
|
253
|
-
For scope/revalidation guarantees and non-guarantees, see:
|
|
254
|
-
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
255
|
-
|
|
256
281
|
## Redirects
|
|
257
282
|
|
|
258
283
|
### Basic redirect
|
|
@@ -383,6 +408,34 @@ urls(({ path, layout }) => [
|
|
|
383
408
|
])
|
|
384
409
|
```
|
|
385
410
|
|
|
411
|
+
## View Transitions
|
|
412
|
+
|
|
413
|
+
A route can configure its own `transition()` — the wrap goes around the route's component itself (routes are leaves; they have no separate default outlet channel). If the route component renders a `<ParallelOutlet />` directly, that slot remains inside the route's VT subtree, so prefer mounting parallel slots in a layout when combining intercept modals with route-level transitions. See [skills/view-transitions](../view-transitions/SKILL.md) for examples and the wrap-location rules across layouts, routes, and slots.
|
|
414
|
+
|
|
415
|
+
## Handler-attached `.use`
|
|
416
|
+
|
|
417
|
+
Page handlers can carry their own loader, middleware, error boundaries, parallels, and other defaults via a `.use` callback — so the page is self-contained and reusable across mount sites without re-wiring the same items.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
const ProductPage: Handler<"/product/:slug"> = async (ctx) => {
|
|
421
|
+
const product = await ctx.use(ProductLoader);
|
|
422
|
+
return <ProductView product={product} />;
|
|
423
|
+
};
|
|
424
|
+
ProductPage.use = () => [
|
|
425
|
+
loader(ProductLoader),
|
|
426
|
+
loading(<ProductSkeleton />),
|
|
427
|
+
middleware(async (ctx, next) => {
|
|
428
|
+
await next();
|
|
429
|
+
ctx.header("Cache-Control", "private, max-age=60");
|
|
430
|
+
}),
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
// Mount site has no per-page wiring — defaults travel with the handler.
|
|
434
|
+
path("/product/:slug", ProductPage, { name: "product" });
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Explicit `use()` at the mount site merges with `handler.use` (handler defaults first, explicit second). See [skills/handler-use](../handler-use/SKILL.md) for the merge order, allowed item types per mount site, and override semantics.
|
|
438
|
+
|
|
386
439
|
## Complete Example
|
|
387
440
|
|
|
388
441
|
```typescript
|
|
@@ -71,7 +71,7 @@ urls(
|
|
|
71
71
|
## Router Options
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
interface
|
|
74
|
+
interface RangoOptions<TEnv> {
|
|
75
75
|
// URL patterns from urls() function
|
|
76
76
|
urls: UrlPatterns;
|
|
77
77
|
|
|
@@ -405,7 +405,7 @@ interface AppBindings {
|
|
|
405
405
|
KV: KVNamespace;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
// Variables declared via
|
|
408
|
+
// Variables declared via global namespace augmentation
|
|
409
409
|
interface AppVariables {
|
|
410
410
|
user?: { id: string; name: string };
|
|
411
411
|
}
|
|
@@ -417,7 +417,7 @@ const router = createRouter<AppBindings>({
|
|
|
417
417
|
|
|
418
418
|
// Register types globally for implicit typing
|
|
419
419
|
declare global {
|
|
420
|
-
namespace
|
|
420
|
+
namespace Rango {
|
|
421
421
|
interface Env extends AppBindings {}
|
|
422
422
|
interface Vars extends AppVariables {}
|
|
423
423
|
}
|