@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fb4fdc18

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 (175) hide show
  1. package/README.md +188 -35
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +1884 -537
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +7 -5
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/cache-guide/SKILL.md +32 -0
  9. package/skills/caching/SKILL.md +8 -0
  10. package/skills/handler-use/SKILL.md +362 -0
  11. package/skills/hooks/SKILL.md +33 -20
  12. package/skills/i18n/SKILL.md +276 -0
  13. package/skills/intercept/SKILL.md +20 -0
  14. package/skills/layout/SKILL.md +22 -0
  15. package/skills/links/SKILL.md +93 -17
  16. package/skills/loader/SKILL.md +123 -46
  17. package/skills/middleware/SKILL.md +36 -3
  18. package/skills/migrate-nextjs/SKILL.md +562 -0
  19. package/skills/migrate-react-router/SKILL.md +769 -0
  20. package/skills/parallel/SKILL.md +133 -0
  21. package/skills/prerender/SKILL.md +110 -68
  22. package/skills/rango/SKILL.md +26 -22
  23. package/skills/response-routes/SKILL.md +8 -0
  24. package/skills/route/SKILL.md +75 -0
  25. package/skills/router-setup/SKILL.md +87 -2
  26. package/skills/server-actions/SKILL.md +739 -0
  27. package/skills/streams-and-websockets/SKILL.md +283 -0
  28. package/skills/typesafety/SKILL.md +19 -1
  29. package/src/__internal.ts +1 -1
  30. package/src/browser/app-shell.ts +52 -0
  31. package/src/browser/app-version.ts +14 -0
  32. package/src/browser/event-controller.ts +44 -4
  33. package/src/browser/navigation-bridge.ts +95 -7
  34. package/src/browser/navigation-client.ts +128 -53
  35. package/src/browser/navigation-store.ts +68 -9
  36. package/src/browser/partial-update.ts +93 -12
  37. package/src/browser/prefetch/cache.ts +129 -21
  38. package/src/browser/prefetch/fetch.ts +156 -18
  39. package/src/browser/prefetch/queue.ts +92 -29
  40. package/src/browser/prefetch/resource-ready.ts +77 -0
  41. package/src/browser/rango-state.ts +53 -13
  42. package/src/browser/react/Link.tsx +72 -8
  43. package/src/browser/react/NavigationProvider.tsx +82 -21
  44. package/src/browser/react/context.ts +7 -2
  45. package/src/browser/react/filter-segment-order.ts +51 -7
  46. package/src/browser/react/use-handle.ts +9 -58
  47. package/src/browser/react/use-navigation.ts +22 -2
  48. package/src/browser/react/use-params.ts +17 -4
  49. package/src/browser/react/use-router.ts +29 -9
  50. package/src/browser/react/use-segments.ts +11 -8
  51. package/src/browser/rsc-router.tsx +60 -9
  52. package/src/browser/scroll-restoration.ts +10 -8
  53. package/src/browser/segment-reconciler.ts +36 -14
  54. package/src/browser/server-action-bridge.ts +8 -6
  55. package/src/browser/types.ts +46 -5
  56. package/src/build/generate-manifest.ts +6 -6
  57. package/src/build/generate-route-types.ts +3 -0
  58. package/src/build/route-trie.ts +52 -25
  59. package/src/build/route-types/include-resolution.ts +8 -1
  60. package/src/build/route-types/router-processing.ts +211 -72
  61. package/src/build/route-types/scan-filter.ts +8 -1
  62. package/src/cache/cache-runtime.ts +15 -11
  63. package/src/cache/cache-scope.ts +46 -5
  64. package/src/cache/cf/cf-cache-store.ts +5 -7
  65. package/src/cache/taint.ts +55 -0
  66. package/src/client.tsx +84 -230
  67. package/src/context-var.ts +72 -2
  68. package/src/handle.ts +40 -0
  69. package/src/index.rsc.ts +6 -1
  70. package/src/index.ts +49 -6
  71. package/src/outlet-context.ts +1 -1
  72. package/src/prerender/store.ts +5 -4
  73. package/src/prerender.ts +138 -77
  74. package/src/response-utils.ts +28 -0
  75. package/src/reverse.ts +28 -2
  76. package/src/route-definition/dsl-helpers.ts +210 -35
  77. package/src/route-definition/helpers-types.ts +73 -20
  78. package/src/route-definition/index.ts +3 -0
  79. package/src/route-definition/redirect.ts +9 -1
  80. package/src/route-definition/resolve-handler-use.ts +155 -0
  81. package/src/route-types.ts +18 -0
  82. package/src/router/content-negotiation.ts +100 -1
  83. package/src/router/handler-context.ts +102 -25
  84. package/src/router/intercept-resolution.ts +9 -4
  85. package/src/router/lazy-includes.ts +6 -6
  86. package/src/router/loader-resolution.ts +159 -21
  87. package/src/router/manifest.ts +22 -13
  88. package/src/router/match-api.ts +128 -192
  89. package/src/router/match-handlers.ts +1 -0
  90. package/src/router/match-middleware/background-revalidation.ts +12 -1
  91. package/src/router/match-middleware/cache-lookup.ts +74 -14
  92. package/src/router/match-middleware/cache-store.ts +21 -4
  93. package/src/router/match-middleware/segment-resolution.ts +53 -0
  94. package/src/router/match-result.ts +112 -9
  95. package/src/router/metrics.ts +6 -1
  96. package/src/router/middleware-types.ts +20 -33
  97. package/src/router/middleware.ts +56 -12
  98. package/src/router/navigation-snapshot.ts +182 -0
  99. package/src/router/pattern-matching.ts +101 -17
  100. package/src/router/prerender-match.ts +110 -10
  101. package/src/router/preview-match.ts +30 -102
  102. package/src/router/request-classification.ts +310 -0
  103. package/src/router/revalidation.ts +15 -1
  104. package/src/router/route-snapshot.ts +245 -0
  105. package/src/router/router-context.ts +1 -0
  106. package/src/router/router-interfaces.ts +36 -4
  107. package/src/router/router-options.ts +37 -11
  108. package/src/router/segment-resolution/fresh.ts +114 -18
  109. package/src/router/segment-resolution/helpers.ts +29 -24
  110. package/src/router/segment-resolution/revalidation.ts +257 -127
  111. package/src/router/trie-matching.ts +18 -13
  112. package/src/router/types.ts +1 -0
  113. package/src/router/url-params.ts +49 -0
  114. package/src/router.ts +55 -7
  115. package/src/rsc/handler.ts +478 -383
  116. package/src/rsc/helpers.ts +69 -41
  117. package/src/rsc/loader-fetch.ts +23 -3
  118. package/src/rsc/manifest-init.ts +5 -1
  119. package/src/rsc/progressive-enhancement.ts +18 -2
  120. package/src/rsc/response-route-handler.ts +14 -1
  121. package/src/rsc/rsc-rendering.ts +20 -1
  122. package/src/rsc/server-action.ts +12 -0
  123. package/src/rsc/ssr-setup.ts +2 -2
  124. package/src/rsc/types.ts +15 -1
  125. package/src/segment-content-promise.ts +67 -0
  126. package/src/segment-loader-promise.ts +122 -0
  127. package/src/segment-system.tsx +22 -62
  128. package/src/server/context.ts +76 -4
  129. package/src/server/handle-store.ts +19 -0
  130. package/src/server/loader-registry.ts +9 -8
  131. package/src/server/request-context.ts +185 -57
  132. package/src/ssr/index.tsx +8 -1
  133. package/src/static-handler.ts +18 -6
  134. package/src/types/cache-types.ts +4 -4
  135. package/src/types/handler-context.ts +145 -68
  136. package/src/types/loader-types.ts +41 -15
  137. package/src/types/request-scope.ts +126 -0
  138. package/src/types/route-entry.ts +12 -1
  139. package/src/types/segments.ts +18 -1
  140. package/src/urls/include-helper.ts +24 -14
  141. package/src/urls/path-helper-types.ts +39 -6
  142. package/src/urls/path-helper.ts +47 -12
  143. package/src/urls/pattern-types.ts +12 -0
  144. package/src/urls/response-types.ts +18 -16
  145. package/src/use-loader.tsx +77 -5
  146. package/src/vite/debug.ts +184 -0
  147. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  148. package/src/vite/discovery/discover-routers.ts +36 -4
  149. package/src/vite/discovery/gate-state.ts +171 -0
  150. package/src/vite/discovery/prerender-collection.ts +175 -74
  151. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  152. package/src/vite/discovery/state.ts +13 -4
  153. package/src/vite/index.ts +4 -0
  154. package/src/vite/plugin-types.ts +60 -5
  155. package/src/vite/plugins/cjs-to-esm.ts +5 -0
  156. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  157. package/src/vite/plugins/client-ref-hashing.ts +16 -4
  158. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  159. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  160. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  161. package/src/vite/plugins/expose-action-id.ts +52 -28
  162. package/src/vite/plugins/expose-id-utils.ts +12 -0
  163. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  164. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  165. package/src/vite/plugins/expose-internal-ids.ts +563 -316
  166. package/src/vite/plugins/performance-tracks.ts +96 -0
  167. package/src/vite/plugins/refresh-cmd.ts +88 -26
  168. package/src/vite/plugins/use-cache-transform.ts +56 -43
  169. package/src/vite/plugins/version-injector.ts +37 -11
  170. package/src/vite/rango.ts +63 -11
  171. package/src/vite/router-discovery.ts +732 -86
  172. package/src/vite/utils/banner.ts +1 -1
  173. package/src/vite/utils/package-resolution.ts +41 -1
  174. package/src/vite/utils/prerender-utils.ts +38 -5
  175. package/src/vite/utils/shared-utils.ts +3 -2
@@ -30,7 +30,11 @@ import {
30
30
  } from "./helpers.js";
31
31
  import { getRouterContext } from "../router-context.js";
32
32
  import { resolveSink, safeEmit } from "../telemetry.js";
33
- import { track } from "../../server/context.js";
33
+ import {
34
+ track,
35
+ RSCRouterContext,
36
+ runInsideLoaderScope,
37
+ } from "../../server/context.js";
34
38
 
35
39
  // ---------------------------------------------------------------------------
36
40
  // Streamed handler telemetry
@@ -100,9 +104,7 @@ export async function resolveLoaders<TEnv>(
100
104
 
101
105
  if (!loadingDisabled) {
102
106
  // Streaming loaders: promises kick off now, settle during RSC serialization.
103
- // No per-loader timing here settlement happens asynchronously during
104
- // RSC/SSR stream consumption, after the perf timeline is logged.
105
- return loaderEntries.map((loaderEntry, i) => {
107
+ const segments = loaderEntries.map((loaderEntry, i) => {
106
108
  const { loader } = loaderEntry;
107
109
  const segmentId = `${shortCode}D${i}.${loader.$$id}`;
108
110
  return {
@@ -114,7 +116,9 @@ export async function resolveLoaders<TEnv>(
114
116
  params: ctx.params,
115
117
  loaderId: loader.$$id,
116
118
  loaderData: deps.wrapLoaderPromise(
117
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
119
+ runInsideLoaderScope(() =>
120
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
121
+ ),
118
122
  entry,
119
123
  segmentId,
120
124
  ctx.pathname,
@@ -122,14 +126,17 @@ export async function resolveLoaders<TEnv>(
122
126
  belongsToRoute,
123
127
  };
124
128
  });
129
+
130
+ return segments;
125
131
  }
126
132
 
127
133
  // Loading disabled: still start all loaders in parallel, but only emit
128
134
  // settled promises so handlers don't stream loading placeholders.
129
- // We can measure actual execution time here since we await all loaders.
130
135
  const pendingLoaderData = loaderEntries.map((loaderEntry) => {
131
136
  const start = performance.now();
132
- const promise = resolveLoaderData(loaderEntry, ctx, ctx.pathname);
137
+ const promise = runInsideLoaderScope(() =>
138
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
139
+ );
133
140
  return { promise, start, loaderId: loaderEntry.loader.$$id };
134
141
  });
135
142
  await Promise.all(pendingLoaderData.map((p) => p.promise));
@@ -277,9 +284,14 @@ export async function resolveSegment<TEnv>(
277
284
  entry.shortCode,
278
285
  );
279
286
  if (component === undefined) {
287
+ // For Passthrough routes at runtime, use the live handler instead of
288
+ // the build handler. At build time (context.build === true), always
289
+ // use the build handler from entry.handler.
290
+ const handler =
291
+ !context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
280
292
  const doneRouteHandler = track(`handler:${entry.id}`, 2);
281
293
  if (entry.loading) {
282
- const result = handleHandlerResult(entry.handler(context));
294
+ const result = handleHandlerResult(handler(context));
283
295
  if (result instanceof Promise) {
284
296
  result.finally(doneRouteHandler).catch(() => {});
285
297
  const tracked = deps.trackHandler(result, {
@@ -300,7 +312,7 @@ export async function resolveSegment<TEnv>(
300
312
  component = result;
301
313
  }
302
314
  } else {
303
- component = handleHandlerResult(await entry.handler(context));
315
+ component = handleHandlerResult(await handler(context));
304
316
  doneRouteHandler();
305
317
  }
306
318
  }
@@ -315,6 +327,7 @@ export async function resolveSegment<TEnv>(
315
327
  deps,
316
328
  options,
317
329
  routeKey,
330
+ entry,
318
331
  );
319
332
  segments.push(...orphanSegments);
320
333
  }
@@ -344,7 +357,7 @@ export async function resolveSegment<TEnv>(
344
357
  namespace: entry.id,
345
358
  type: "route",
346
359
  index: 0,
347
- component,
360
+ component: component ?? null,
348
361
  loading: entry.loading === false ? null : entry.loading,
349
362
  transition: entry.transition,
350
363
  params,
@@ -370,6 +383,9 @@ export async function resolveOrphanLayout<TEnv>(
370
383
  deps: SegmentResolutionDeps<TEnv>,
371
384
  options?: ResolveSegmentOptions,
372
385
  routeKey?: string,
386
+ /** Parent route entry — its loaders are inherited by the layout so
387
+ * parallel slots inside this layout can access them via useLoader(). */
388
+ parentRouteEntry?: EntryData,
373
389
  ): Promise<ResolvedSegment[]> {
374
390
  invariant(
375
391
  orphan.type === "layout" || orphan.type === "cache",
@@ -385,6 +401,30 @@ export async function resolveOrphanLayout<TEnv>(
385
401
  deps,
386
402
  );
387
403
  segments.push(...loaderSegments);
404
+
405
+ // Inherit parent route's loaders so parallel slots inside this layout
406
+ // can access them via useLoader(). Without this, the route's loaders
407
+ // are only in the route's OutletProvider (rendered as <Outlet /> content),
408
+ // which is a child — not a parent — of the layout's context.
409
+ if (
410
+ parentRouteEntry &&
411
+ parentRouteEntry.loader &&
412
+ parentRouteEntry.loader.length > 0 &&
413
+ Object.keys(orphan.parallel).length > 0
414
+ ) {
415
+ const inheritedLoaders = await resolveLoaders(
416
+ parentRouteEntry,
417
+ context,
418
+ belongsToRoute,
419
+ deps,
420
+ orphan.shortCode,
421
+ );
422
+ // Tag as inherited so buildMatchResult can deduplicate when safe
423
+ for (const s of inheritedLoaders) {
424
+ s._inherited = true;
425
+ }
426
+ segments.push(...inheritedLoaders);
427
+ }
388
428
  }
389
429
 
390
430
  // Handler-first: orphan layout handler executes before its parallels
@@ -475,6 +515,14 @@ export async function resolveParallelEntry<TEnv>(
475
515
  if (handler === undefined) {
476
516
  continue;
477
517
  }
518
+ // Pin `_currentSegmentId` to the slot's own id so handle pushes from
519
+ // inside the slot handler get their own bucket in the HandleStore.
520
+ // Parent-keying would collapse them into the parent layout's bucket;
521
+ // the partial-update merge then replaces the parent's bucket on a
522
+ // slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
523
+ // filterSegmentOrder() retains slot ids so the client preserves them.
524
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
525
+ `${parentShortCode}.${slot}`;
478
526
  const doneParallelHandler = track(
479
527
  `handler:${parallelEntry.id}.${slot}`,
480
528
  2,
@@ -580,6 +628,13 @@ export async function resolveAllSegments<TEnv>(
580
628
  } catch {}
581
629
 
582
630
  for (const entry of entries) {
631
+ // Set ALS flag when entering a cache() boundary so that ctx.get()
632
+ // can guard non-cacheable variable reads. Also guards response-level
633
+ // side effects (headers.set). Persists for all descendant entries.
634
+ if (entry.type === "cache") {
635
+ const store = RSCRouterContext.getStore();
636
+ if (store) store.insideCacheScope = true;
637
+ }
583
638
  const doneEntry = track(`segment:${entry.id}`, 1);
584
639
  const resolvedSegments = await resolveWithErrorBoundary(
585
640
  entry,
@@ -624,20 +679,37 @@ export async function resolveLoadersOnly<TEnv>(
624
679
  deps: SegmentResolutionDeps<TEnv>,
625
680
  ): Promise<ResolvedSegment[]> {
626
681
  const loaderSegments: ResolvedSegment[] = [];
682
+ const seenIds = new Set<string>();
627
683
 
628
684
  async function collectEntryLoaders(
629
685
  entry: EntryData,
630
686
  belongsToRoute: boolean,
631
687
  shortCodeOverride?: string,
632
688
  ): Promise<void> {
633
- const segments = await resolveLoaders(
634
- entry,
635
- context,
636
- belongsToRoute,
637
- deps,
638
- shortCodeOverride,
639
- );
640
- loaderSegments.push(...segments);
689
+ // Skip if all loaders from this entry have already been resolved
690
+ // via a parent (e.g., cache boundary wrapping a layout with shared loaders).
691
+ const entryLoaders = entry.loader ?? [];
692
+ const sc = shortCodeOverride ?? entry.shortCode;
693
+ const allAlreadySeen =
694
+ entryLoaders.length > 0 &&
695
+ entryLoaders.every((le, i) =>
696
+ seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
697
+ );
698
+ if (!allAlreadySeen) {
699
+ const segments = await resolveLoaders(
700
+ entry,
701
+ context,
702
+ belongsToRoute,
703
+ deps,
704
+ shortCodeOverride,
705
+ );
706
+ for (const seg of segments) {
707
+ if (!seenIds.has(seg.id)) {
708
+ seenIds.add(seg.id);
709
+ loaderSegments.push(seg);
710
+ }
711
+ }
712
+ }
641
713
 
642
714
  const seenParallelEntryIds = new Set<string>();
643
715
  for (const parallelEntry of getParallelEntries(entry.parallel)) {
@@ -649,6 +721,30 @@ export async function resolveLoadersOnly<TEnv>(
649
721
  const childBelongsToRoute = belongsToRoute || entry.type === "route";
650
722
  for (const layoutEntry of entry.layout) {
651
723
  await collectEntryLoaders(layoutEntry, childBelongsToRoute);
724
+ // Inherit route loaders for orphan layouts with parallels.
725
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
726
+ // route entry, as that would re-iterate route.layout and loop.
727
+ if (
728
+ entry.type === "route" &&
729
+ entry.loader &&
730
+ entry.loader.length > 0 &&
731
+ Object.keys(layoutEntry.parallel).length > 0
732
+ ) {
733
+ const inherited = await resolveLoaders(
734
+ entry,
735
+ context,
736
+ childBelongsToRoute,
737
+ deps,
738
+ layoutEntry.shortCode,
739
+ );
740
+ for (const seg of inherited) {
741
+ if (!seenIds.has(seg.id)) {
742
+ seenIds.add(seg.id);
743
+ seg._inherited = true;
744
+ loaderSegments.push(seg);
745
+ }
746
+ }
747
+ }
652
748
  }
653
749
  }
654
750
 
@@ -8,7 +8,7 @@
8
8
  * - Error boundary segment creation
9
9
  */
10
10
 
11
- import type { ReactNode } from "react";
11
+ import { createElement, type ReactNode } from "react";
12
12
  import { DataNotFoundError } from "../../errors";
13
13
  import {
14
14
  createErrorInfo,
@@ -180,34 +180,39 @@ export function catchSegmentError<TEnv>(
180
180
 
181
181
  if (error instanceof DataNotFoundError) {
182
182
  const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
183
+ // Fall back to router's notFound component, then a plain default
184
+ const notFoundOption = deps.notFoundComponent;
185
+ const defaultFallback =
186
+ typeof notFoundOption === "function"
187
+ ? notFoundOption({ pathname: pathname ?? "" })
188
+ : (notFoundOption ?? createElement("h1", null, "Not Found"));
189
+ const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
183
190
 
184
- if (notFoundFallback) {
185
- const notFoundInfo = createNotFoundInfo(
186
- error,
187
- entry.shortCode,
188
- entry.type,
189
- pathname,
190
- );
191
+ const notFoundInfo = createNotFoundInfo(
192
+ error,
193
+ entry.shortCode,
194
+ entry.type,
195
+ pathname,
196
+ );
191
197
 
192
- reportError(true, {
193
- notFound: true,
194
- message: notFoundInfo.message,
195
- });
198
+ reportError(true, {
199
+ notFound: true,
200
+ message: notFoundInfo.message,
201
+ });
196
202
 
197
- debugLog("segment", "notFound boundary handled error", {
198
- segmentId: entry.shortCode,
199
- message: notFoundInfo.message,
200
- });
203
+ debugLog("segment", "notFound boundary handled error", {
204
+ segmentId: entry.shortCode,
205
+ message: notFoundInfo.message,
206
+ });
201
207
 
202
- setResponseStatus(404);
208
+ setResponseStatus(404);
203
209
 
204
- return createNotFoundSegment(
205
- notFoundInfo,
206
- notFoundFallback,
207
- entry,
208
- params,
209
- );
210
- }
210
+ return createNotFoundSegment(
211
+ notFoundInfo,
212
+ effectiveNotFoundFallback,
213
+ entry,
214
+ params,
215
+ );
211
216
  }
212
217
 
213
218
  const fallback = deps.findNearestErrorBoundary(entry);