@rangojs/router 0.0.0-experimental.133 → 0.0.0-experimental.135

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.
Files changed (71) hide show
  1. package/dist/bin/rango.js +7 -2
  2. package/dist/vite/index.js +41 -27
  3. package/package.json +23 -24
  4. package/skills/composability/SKILL.md +0 -1
  5. package/skills/handler-use/SKILL.md +7 -7
  6. package/skills/intercept/SKILL.md +38 -13
  7. package/skills/loader/SKILL.md +10 -0
  8. package/skills/migrate-nextjs/SKILL.md +3 -3
  9. package/skills/migrate-react-router/SKILL.md +144 -1
  10. package/skills/prerender/SKILL.md +20 -17
  11. package/skills/router-setup/SKILL.md +1 -2
  12. package/skills/testing/SKILL.md +1 -0
  13. package/skills/testing/render-handler.md +15 -14
  14. package/skills/use-cache/SKILL.md +11 -0
  15. package/skills/view-transitions/SKILL.md +43 -0
  16. package/src/browser/navigation-bridge.ts +65 -16
  17. package/src/browser/navigation-client.ts +27 -1
  18. package/src/browser/navigation-store.ts +82 -8
  19. package/src/browser/network-error-handler.ts +34 -7
  20. package/src/browser/partial-update.ts +43 -3
  21. package/src/browser/prefetch/cache.ts +8 -0
  22. package/src/browser/prefetch/fetch.ts +32 -4
  23. package/src/browser/react/NavigationProvider.tsx +195 -4
  24. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  25. package/src/browser/response-adapter.ts +38 -9
  26. package/src/browser/types.ts +32 -1
  27. package/src/cache/cache-runtime.ts +26 -5
  28. package/src/cache/document-cache.ts +17 -1
  29. package/src/cache/profile-registry.ts +15 -0
  30. package/src/cache/read-through-swr.ts +15 -1
  31. package/src/handles/MetaTags.tsx +6 -0
  32. package/src/index.rsc.ts +6 -1
  33. package/src/index.ts +6 -4
  34. package/src/internal-debug.ts +11 -8
  35. package/src/render-error-thrower.tsx +20 -0
  36. package/src/route-content-wrapper.tsx +12 -5
  37. package/src/route-definition/dsl-helpers.ts +21 -32
  38. package/src/route-definition/helper-factories.ts +0 -2
  39. package/src/route-definition/helpers-types.ts +38 -39
  40. package/src/route-definition/index.ts +1 -2
  41. package/src/route-definition/resolve-handler-use.ts +0 -1
  42. package/src/route-definition/use-item-types.ts +3 -6
  43. package/src/route-types.ts +0 -5
  44. package/src/router/match-api.ts +5 -1
  45. package/src/router/match-middleware/background-revalidation.ts +40 -23
  46. package/src/router/match-middleware/cache-store.ts +39 -24
  47. package/src/router/segment-resolution/fresh.ts +4 -0
  48. package/src/router/segment-resolution/loader-cache.ts +14 -2
  49. package/src/router/segment-resolution/revalidation.ts +3 -0
  50. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  51. package/src/rsc/progressive-enhancement.ts +56 -2
  52. package/src/rsc/rsc-rendering.ts +7 -2
  53. package/src/rsc/server-action.ts +25 -2
  54. package/src/rsc/transition-gate.ts +89 -0
  55. package/src/segment-system.tsx +59 -8
  56. package/src/server/context.ts +13 -0
  57. package/src/server/loader-registry.ts +13 -1
  58. package/src/server/request-context.ts +52 -3
  59. package/src/testing/index.ts +6 -0
  60. package/src/testing/render-handler.ts +14 -0
  61. package/src/testing/run-transition-when.ts +164 -0
  62. package/src/types/handler-context.ts +1 -1
  63. package/src/types/index.ts +2 -0
  64. package/src/types/segments.ts +100 -0
  65. package/src/urls/path-helper-types.ts +10 -7
  66. package/src/urls/urls-function.ts +0 -1
  67. package/src/vite/inject-client-debug.ts +36 -0
  68. package/src/vite/plugins/version-injector.ts +22 -7
  69. package/src/vite/plugins/virtual-entries.ts +28 -9
  70. package/src/vite/router-discovery.ts +8 -13
  71. package/src/network-error-thrower.tsx +0 -18
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bake the resolved INTERNAL_RANGO_DEBUG value into the router's `internal-debug`
3
+ * module so the flag reaches the CLIENT debug logs by just setting the env var.
4
+ *
5
+ * internal-debug.ts normally reads the flag via `typeof __RANGO_DEBUG__`, a Vite
6
+ * define. That delivery is unreliable on the client: in dev Vite ships the define
7
+ * only as an injected global whose presence varies across consumer setups, so the
8
+ * module can fall through to `process.env` (undefined in the browser) and the FE
9
+ * debug flag silently stays false while the server logs work. A Vite `transform`
10
+ * runs on the module regardless of how (or whether) the define is delivered, in
11
+ * both dev and build and for every environment, so the discovery plugin uses this
12
+ * to replace the module with the resolved literal.
13
+ *
14
+ * Returns null for any module that is not the router's internal-debug module.
15
+ */
16
+ export function injectClientDebugFlag(
17
+ id: string,
18
+ ): { code: string; map: null } | null {
19
+ // Cheap early-out: this hook runs on every module in every environment.
20
+ if (!id.includes("internal-debug")) return null;
21
+ const norm = id.replace(/\\/g, "/");
22
+ // Scope to the router's own internal-debug module: the published package
23
+ // (`/@rangojs/router/`, incl. pnpm's nested layout) or the monorepo workspace
24
+ // (`/packages/rangojs-router/`). The package-anchored path avoids matching a
25
+ // consumer file that merely sits under a directory named `rangojs-router`.
26
+ const isInternalDebug =
27
+ /\/internal-debug\.[cm]?[jt]sx?(\?|$)/.test(norm) &&
28
+ (norm.includes("/@rangojs/router/") ||
29
+ norm.includes("/packages/rangojs-router/"));
30
+ if (!isInternalDebug) return null;
31
+ // Emit the whole module: internal-debug.ts has a single export, kept in sync.
32
+ return {
33
+ code: `export const INTERNAL_RANGO_DEBUG = ${!!process.env.INTERNAL_RANGO_DEBUG};\n`,
34
+ map: null,
35
+ };
36
+ }
@@ -2,12 +2,25 @@ import type { Plugin } from "vite";
2
2
  import { resolve } from "node:path";
3
3
  import * as Vite from "vite";
4
4
  import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
5
+ import { RSC_ENTRY_BOOTSTRAP_IMPORTS } from "./virtual-entries.js";
5
6
 
6
7
  /**
7
- * Plugin that auto-injects VERSION and routes-manifest into custom entry.rsc files.
8
- * If a custom entry.rsc file uses createRSCHandler but doesn't pass version,
9
- * this transform adds the import and property automatically.
10
- * Also ensures the routes-manifest virtual module is always imported.
8
+ * Plugin that auto-injects VERSION, routes-manifest, and loader-manifest into
9
+ * custom entry.rsc files. If a custom entry.rsc file uses createRSCHandler but
10
+ * doesn't pass version, this transform adds the import and property
11
+ * automatically. It also ensures the routes-manifest and loader-manifest
12
+ * virtual modules are always imported.
13
+ *
14
+ * The loader-manifest import is what makes fetchable loaders resolvable on a
15
+ * custom worker entry. The virtual RSC entry (getVirtualEntryRSC) imports the
16
+ * loader manifest itself, but a hand-written entry (e.g. a Cloudflare
17
+ * worker.rsc.tsx) does not — so without this injection, setLoaderImports() is
18
+ * never bundled, lazyLoaderImports stays null at runtime, and a fetchable
19
+ * loader that is never imported by the server graph (not registered via
20
+ * loader(), reachable only through a client component) cannot be found by the
21
+ * _rsc_loader endpoint in production. Dev still works because it resolves
22
+ * loaders by parsing their id into a file path; only production relied on the
23
+ * manifest, hence the production-only failure this fixes.
11
24
  * @internal
12
25
  */
13
26
  export function createVersionInjectorPlugin(
@@ -36,9 +49,11 @@ export function createVersionInjectorPlugin(
36
49
  return null;
37
50
  }
38
51
 
39
- const prepend: string[] = [
40
- `import "virtual:rsc-router/routes-manifest";`,
41
- ];
52
+ // Same startup bootstrap imports the generated virtual RSC entry uses,
53
+ // from the single shared list so the two paths cannot drift.
54
+ const prepend: string[] = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
55
+ (id) => `import "${id}";`,
56
+ );
42
57
 
43
58
  let newCode = code;
44
59
  const needsVersion =
@@ -51,7 +51,32 @@ export const renderHTML = createSSRHandler({
51
51
  });
52
52
  `.trim();
53
53
 
54
+ /**
55
+ * Virtual modules an RSC entry must import at startup to register the data the
56
+ * request handler needs before the first request arrives:
57
+ *
58
+ * - routes-manifest: the pre-generated route map so href()/matching work on a
59
+ * cold start (full map in build, in-memory no-op in dev).
60
+ * - loader-manifest: setLoaderImports() for fetchable loaders. Critical for
61
+ * serverless/multi-process deployments and for fetchable loaders reachable
62
+ * only through a client component — without it those loaders are never
63
+ * registered for the _rsc_loader endpoint and fail in production.
64
+ *
65
+ * Single source of truth: both the generated virtual RSC entry below and the
66
+ * custom-entry injector (version-injector) consume this list, so a new
67
+ * bootstrap manifest cannot be added to one path and forgotten on the other.
68
+ * That exact drift (loader-manifest present here but missing from the injector)
69
+ * is what left fetchable loaders unresolved on custom worker entries.
70
+ */
71
+ export const RSC_ENTRY_BOOTSTRAP_IMPORTS: readonly string[] = [
72
+ "virtual:rsc-router/routes-manifest",
73
+ "virtual:rsc-router/loader-manifest",
74
+ ];
75
+
54
76
  export function getVirtualEntryRSC(routerPath: string): string {
77
+ const bootstrapImports = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
78
+ (id) => `import "${id}";`,
79
+ ).join("\n");
55
80
  return `
56
81
  import {
57
82
  renderToReadableStream,
@@ -65,15 +90,9 @@ import { router } from "${routerPath}";
65
90
  import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
66
91
  import { VERSION } from "@rangojs/router:version";
67
92
 
68
- // Import loader manifest to ensure all fetchable loaders are registered at startup
69
- // This is critical for serverless/multi-process deployments where the loader module
70
- // might not be imported before a GET request arrives
71
- import "virtual:rsc-router/loader-manifest";
72
-
73
- // Import pre-generated route manifest so href() works immediately on cold start.
74
- // In build mode, this contains the full route map generated at build time.
75
- // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
76
- import "virtual:rsc-router/routes-manifest";
93
+ // Startup bootstrap imports (routes + loader manifests). See
94
+ // RSC_ENTRY_BOOTSTRAP_IMPORTS — the same list the custom-entry injector uses.
95
+ ${bootstrapImports}
77
96
 
78
97
  // Lazily create the handler on first request so that ESM live bindings
79
98
  // have resolved by the time we read \`router\`. During HMR the module may
@@ -19,6 +19,7 @@ import {
19
19
  createScanFilter,
20
20
  } from "../build/generate-route-types.js";
21
21
  import { firstCodeMatchIndex } from "../build/route-types/source-scan.js";
22
+ import { injectClientDebugFlag } from "./inject-client-debug.js";
22
23
  import { createVersionPlugin } from "./plugins/version-plugin.js";
23
24
  import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
24
25
  import {
@@ -310,19 +311,13 @@ export function createRouterDiscoveryPlugin(
310
311
  return {
311
312
  name: "@rangojs/router:discovery",
312
313
 
313
- config() {
314
- const config: any = {
315
- define: {
316
- __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG),
317
- },
318
- };
319
- // Prerender/static handler modules are bundled naturally with the
320
- // rest of the RSC entry. A previous design forced them into dedicated
321
- // __prerender-handlers / __static-handlers chunks via manualChunks,
322
- // but Rollup hoisted all shared dependencies into those chunks,
323
- // inflating them to ~1 MB with active runtime code. Handler code is
324
- // evicted in closeBundle regardless of which chunk it lands in.
325
- return config;
314
+ // Make INTERNAL_RANGO_DEBUG reach the CLIENT debug logs by just setting the
315
+ // env var. See injectClientDebugFlag: bakes the resolved flag into the
316
+ // internal-debug module so FE debug no longer depends on Vite delivering the
317
+ // `__RANGO_DEBUG__` define to the client (which it does only as an injected
318
+ // global whose presence varies across consumer setups). Runs in dev and build.
319
+ transform(_code, id) {
320
+ return injectClientDebugFlag(id);
326
321
  },
327
322
 
328
323
  configResolved(config) {
@@ -1,18 +0,0 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
- import type { NetworkError } from "./errors.js";
5
-
6
- interface NetworkErrorThrowerProps {
7
- error: NetworkError;
8
- }
9
-
10
- /**
11
- * Client component that throws a NetworkError during render.
12
- * Errors thrown during render are caught by error boundaries; async errors are not.
13
- */
14
- export function NetworkErrorThrower({
15
- error,
16
- }: NetworkErrorThrowerProps): ReactNode {
17
- throw error;
18
- }