@rangojs/router 0.22.1 → 0.23.0

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 (179) hide show
  1. package/dist/bin/rango.js +8 -3
  2. package/dist/testing/vitest.js +31 -1
  3. package/dist/types/browser/defer.d.ts +48 -0
  4. package/dist/types/browser/document-revealed.d.ts +17 -0
  5. package/dist/types/browser/event-controller.d.ts +7 -1
  6. package/dist/types/browser/navigation-transaction.d.ts +21 -7
  7. package/dist/types/browser/partial-update.d.ts +34 -5
  8. package/dist/types/browser/react/NavigationProvider.d.ts +13 -3
  9. package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
  10. package/dist/types/browser/response-adapter.d.ts +7 -0
  11. package/dist/types/browser/types.d.ts +19 -9
  12. package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
  13. package/dist/types/build/generate-route-types.d.ts +1 -1
  14. package/dist/types/build/route-types/router-processing.d.ts +1 -1
  15. package/dist/types/build/route-types/scan-filter.d.ts +16 -2
  16. package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
  17. package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
  18. package/dist/types/client-urls/client-urls.d.ts +1 -1
  19. package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
  20. package/dist/types/client-urls/navigation.d.ts +0 -37
  21. package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
  22. package/dist/types/client-urls/server-projection.d.ts +29 -1
  23. package/dist/types/client-urls/types.d.ts +34 -69
  24. package/dist/types/client.d.ts +0 -1
  25. package/dist/types/errors.d.ts +5 -0
  26. package/dist/types/handles/deferred-resolution.d.ts +2 -1
  27. package/dist/types/handles/pushed-by.d.ts +23 -0
  28. package/dist/types/loader-store.d.ts +8 -0
  29. package/dist/types/prefetch-markers.d.ts +11 -0
  30. package/dist/types/route-definition/helpers-types.d.ts +27 -0
  31. package/dist/types/router/is-action.d.ts +2 -1
  32. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
  33. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +241 -0
  34. package/dist/types/server/context.d.ts +21 -1
  35. package/dist/types/server/handle-store.d.ts +9 -1
  36. package/dist/types/server/request-context.d.ts +14 -1
  37. package/dist/types/server.d.ts +1 -0
  38. package/dist/types/testing/flight.entry.d.ts +1 -1
  39. package/dist/types/testing/index.d.ts +3 -3
  40. package/dist/types/testing/internal/action-id.d.ts +10 -1
  41. package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
  42. package/dist/types/testing/internal/test-action.d.ts +8 -0
  43. package/dist/types/testing/run-loader.d.ts +2 -1
  44. package/dist/types/testing/run-revalidate.d.ts +74 -0
  45. package/dist/types/testing/run-transition-when.d.ts +2 -3
  46. package/dist/types/testing/serve-shell-request.d.ts +38 -0
  47. package/dist/types/testing/vitest.d.ts +11 -0
  48. package/dist/types/types/boundaries.d.ts +9 -0
  49. package/dist/types/types/handler-context.d.ts +14 -0
  50. package/dist/types/types/loader-types.d.ts +30 -0
  51. package/dist/types/types/segments.d.ts +21 -4
  52. package/dist/types/urls/path-helper-types.d.ts +25 -4
  53. package/dist/types/urls/type-extraction.d.ts +6 -2
  54. package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
  55. package/dist/types/vite/discovery/state.d.ts +11 -4
  56. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
  57. package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
  58. package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
  59. package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
  60. package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
  61. package/dist/types/vite/router-discovery.d.ts +10 -0
  62. package/dist/vite/index.js +842 -308
  63. package/package.json +1 -1
  64. package/skills/caching/SKILL.md +42 -5
  65. package/skills/catalog.json +2 -2
  66. package/skills/client-urls/SKILL.md +113 -24
  67. package/skills/cloudflare/SKILL.md +14 -9
  68. package/skills/hooks/SKILL.md +11 -3
  69. package/skills/links/SKILL.md +24 -0
  70. package/skills/loader/SKILL.md +192 -1
  71. package/skills/migrate-nextjs/SKILL.md +2 -2
  72. package/skills/migrate-react-router/SKILL.md +10 -7
  73. package/skills/migrate-react-router/data-and-actions.md +1 -1
  74. package/skills/ppr/SKILL.md +13 -0
  75. package/skills/prerender/SKILL.md +22 -8
  76. package/skills/rango/SKILL.md +12 -6
  77. package/skills/route/SKILL.md +139 -0
  78. package/skills/testing/SKILL.md +3 -2
  79. package/skills/testing/client-components.md +1 -1
  80. package/skills/testing/predicates.md +67 -32
  81. package/skills/testing/serve-shell-request.md +88 -18
  82. package/skills/testing/setup.md +2 -2
  83. package/skills/typesafety/route-types.md +2 -2
  84. package/skills/view-transitions/SKILL.md +26 -0
  85. package/src/browser/defer.ts +82 -0
  86. package/src/browser/document-revealed.ts +52 -0
  87. package/src/browser/event-controller.ts +43 -32
  88. package/src/browser/navigation-bridge.ts +64 -14
  89. package/src/browser/navigation-client.ts +48 -36
  90. package/src/browser/navigation-store.ts +13 -0
  91. package/src/browser/navigation-transaction.ts +25 -8
  92. package/src/browser/partial-update.ts +528 -135
  93. package/src/browser/prefetch/fetch.ts +2 -1
  94. package/src/browser/react/NavigationProvider.tsx +47 -28
  95. package/src/browser/react/filter-segment-order.ts +13 -2
  96. package/src/browser/response-adapter.ts +20 -0
  97. package/src/browser/rsc-router.tsx +17 -45
  98. package/src/browser/server-action-bridge.ts +19 -63
  99. package/src/browser/types.ts +19 -9
  100. package/src/browser/validate-redirect-origin.ts +16 -0
  101. package/src/build/generate-route-types.ts +1 -0
  102. package/src/build/route-types/router-processing.ts +3 -6
  103. package/src/build/route-types/scan-filter.ts +37 -10
  104. package/src/cache/cf/cf-cache-store.ts +59 -21
  105. package/src/cache/cf/cf-cache-types.ts +24 -2
  106. package/src/cache/document-cache.ts +22 -1
  107. package/src/client-urls/client-urls.ts +22 -6
  108. package/src/client-urls/hoisted-revalidate.ts +25 -0
  109. package/src/client-urls/navigation.ts +0 -96
  110. package/src/client-urls/revalidate-registry.ts +30 -0
  111. package/src/client-urls/server-projection.ts +151 -65
  112. package/src/client-urls/types.ts +39 -71
  113. package/src/client.tsx +9 -5
  114. package/src/errors.ts +15 -0
  115. package/src/handles/deferred-resolution.ts +28 -5
  116. package/src/handles/pushed-by.ts +122 -0
  117. package/src/loader-store.ts +18 -7
  118. package/src/prefetch-markers.ts +13 -0
  119. package/src/route-content-wrapper.tsx +18 -4
  120. package/src/route-definition/dsl-helpers.ts +27 -4
  121. package/src/route-definition/helpers-types.ts +27 -1
  122. package/src/router/intercept-resolution.ts +6 -4
  123. package/src/router/is-action.ts +2 -1
  124. package/src/router/loader-resolution.ts +83 -4
  125. package/src/router/match-api.ts +36 -1
  126. package/src/router/match-middleware/background-revalidation.ts +11 -1
  127. package/src/router/match-middleware/cache-lookup.ts +29 -6
  128. package/src/router/match-middleware/cache-store.ts +17 -1
  129. package/src/router/match-result.ts +14 -2
  130. package/src/router/navigation-snapshot.ts +2 -1
  131. package/src/router/segment-resolution/fresh.ts +22 -18
  132. package/src/router/segment-resolution/loader-cache.ts +2 -1
  133. package/src/router/segment-resolution/loader-mask.ts +5 -3
  134. package/src/router/segment-resolution/prefetch-deferral.ts +543 -0
  135. package/src/router/segment-resolution/revalidation.ts +169 -60
  136. package/src/rsc/rsc-rendering.ts +50 -7
  137. package/src/rsc/shell-capture.ts +2 -1
  138. package/src/segment-loader-promise.ts +23 -20
  139. package/src/segment-system.tsx +60 -26
  140. package/src/server/context.ts +39 -5
  141. package/src/server/handle-store.ts +104 -8
  142. package/src/server/request-context.ts +21 -1
  143. package/src/server.ts +4 -0
  144. package/src/testing/action-form.ts +2 -11
  145. package/src/testing/e2e/page-helpers.ts +3 -1
  146. package/src/testing/flight.entry.ts +1 -0
  147. package/src/testing/index.ts +6 -3
  148. package/src/testing/internal/action-id.ts +16 -1
  149. package/src/testing/internal/client-urls-projection.ts +70 -0
  150. package/src/testing/internal/test-action.ts +25 -0
  151. package/src/testing/run-loader.ts +2 -1
  152. package/src/testing/run-revalidate.ts +142 -0
  153. package/src/testing/run-transition-when.ts +7 -8
  154. package/src/testing/serve-shell-request.ts +116 -6
  155. package/src/testing/vitest.ts +67 -1
  156. package/src/types/boundaries.ts +12 -0
  157. package/src/types/handler-context.ts +14 -0
  158. package/src/types/loader-types.ts +30 -0
  159. package/src/types/segments.ts +26 -4
  160. package/src/urls/path-helper-types.ts +27 -5
  161. package/src/urls/type-extraction.ts +30 -10
  162. package/src/use-loader.tsx +18 -13
  163. package/src/vite/discovery/client-urls-projection.ts +20 -21
  164. package/src/vite/discovery/discover-routers.ts +94 -26
  165. package/src/vite/discovery/state.ts +20 -4
  166. package/src/vite/discovery/virtual-module-codegen.ts +22 -17
  167. package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
  168. package/src/vite/plugins/hoist/literal.ts +417 -0
  169. package/src/vite/plugins/hoist/scope.ts +370 -0
  170. package/src/vite/plugins/hoist/source.ts +84 -0
  171. package/src/vite/plugins/transition-when-hoist.ts +113 -779
  172. package/src/vite/rango.ts +2 -0
  173. package/src/vite/router-discovery.ts +93 -32
  174. package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
  175. package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
  176. package/dist/types/testing/run-client-revalidate.d.ts +0 -43
  177. package/src/client-urls/revalidate-chain.ts +0 -83
  178. package/src/client-urls/revalidation-protocol.ts +0 -56
  179. package/src/testing/run-client-revalidate.ts +0 -92
@@ -99,6 +99,10 @@ import { observeEvent } from "../instrument.js";
99
99
  import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
100
100
  import { treeHasStreaming } from "./segment-resolution.js";
101
101
  import { loaderPins } from "../segment-resolution/loader-cache.js";
102
+ import {
103
+ defersAboveRecord,
104
+ isFillRequest,
105
+ } from "../segment-resolution/prefetch-deferral.js";
102
106
  import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
103
107
  import {
104
108
  isStoredEntryStale,
@@ -342,15 +346,18 @@ async function* yieldFromStore<TEnv>(
342
346
  const paramsChanged =
343
347
  !ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
344
348
  const resendRouteSegments = replaceable === true && ctx.isAction;
349
+ // A fill (prefetch-deferral.ts) keeps every segment the client holds.
350
+ const fill = isFillRequest(ctx.handlerContext);
345
351
  for (const segment of segments) {
346
352
  if (
347
353
  !ctx.isFullMatch &&
348
- !paramsChanged &&
349
354
  ctx.clientSegmentSet.has(segment.id) &&
350
- !(
351
- resendRouteSegments &&
352
- (segment.type === "route" || segment.belongsToRoute === true)
353
- )
355
+ (fill ||
356
+ (!paramsChanged &&
357
+ !(
358
+ resendRouteSegments &&
359
+ (segment.type === "route" || segment.belongsToRoute === true)
360
+ )))
354
361
  ) {
355
362
  keepClientSegment(segment);
356
363
  }
@@ -725,7 +732,15 @@ export function withCacheLookup<TEnv>(
725
732
  throw new ShellRecordUnavailableError(tailMarker.fixedDocKey);
726
733
  }
727
734
 
728
- if (ctx.isAction || !ctx.cacheScope?.enabled) {
735
+ // A prefetch that defers a layout above the boundary (prefetch: false)
736
+ // emits nothing the record holds: reading it would replay the handle
737
+ // pushes of segments the response does not carry. withCacheStore skips
738
+ // the write for the same request.
739
+ if (
740
+ ctx.isAction ||
741
+ !ctx.cacheScope?.enabled ||
742
+ defersAboveRecord(ctx.handlerContext)
743
+ ) {
729
744
  yield* source;
730
745
  if (ms) {
731
746
  ms.metrics.push({
@@ -897,6 +912,7 @@ export function withCacheLookup<TEnv>(
897
912
 
898
913
  yield* liveSegments;
899
914
 
915
+ const fill = isFillRequest(ctx.handlerContext);
900
916
  for (const segment of cacheResult.segments) {
901
917
  if (!ctx.clientSegmentSet.has(segment.id)) {
902
918
  if (isTraceActive()) {
@@ -919,6 +935,13 @@ export function withCacheLookup<TEnv>(
919
935
  continue;
920
936
  }
921
937
 
938
+ // A fill (prefetch-deferral.ts) skips a held segment outright.
939
+ if (fill) {
940
+ keepClientSegment(segment);
941
+ yield segment;
942
+ continue;
943
+ }
944
+
922
945
  const entryInfo = entryRevalidateMap?.get(segment.id);
923
946
 
924
947
  const searchChanged = ctx.prevUrl.search !== ctx.url.search;
@@ -114,6 +114,10 @@ import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
114
114
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
115
115
  import type { GeneratorMiddleware } from "./cache-lookup.js";
116
116
  import { rerenderAndCacheRoute } from "./background-revalidation.js";
117
+ import {
118
+ defersAboveRecord,
119
+ isFillRequest,
120
+ } from "../segment-resolution/prefetch-deferral.js";
117
121
 
118
122
  /**
119
123
  * Creates cache store middleware
@@ -149,11 +153,15 @@ export function withCacheStore<TEnv>(
149
153
  // segments were collected into allSegments either way).
150
154
  recordShellCaptureDocRecord(ctx, state, allSegments);
151
155
 
156
+ // prefetch: false (prefetch-deferral.ts). A prefetch that deferred a
157
+ // layout above the boundary did not read the record (withCacheLookup)
158
+ // and could only write an incomplete one.
152
159
  if (
153
160
  !ctx.cacheScope?.enabled ||
154
161
  ctx.isAction ||
155
162
  state.cacheHit ||
156
- ctx.request.method !== "GET"
163
+ ctx.request.method !== "GET" ||
164
+ defersAboveRecord(ctx.handlerContext)
157
165
  ) {
158
166
  if (ms) {
159
167
  ms.metrics.push({
@@ -192,6 +200,14 @@ export function withCacheStore<TEnv>(
192
200
  cacheScope.covers(s.id, s.namespace),
193
201
  );
194
202
 
203
+ // A fill renders what the client is missing and nothing else. When that
204
+ // is everything the record covers, it writes like any other request.
205
+ // When the client holds part of it, the write would be partial, and the
206
+ // proactive re-render below would run handlers a fill must not run. The
207
+ // stale-hit refresh skips a fill for the same reason
208
+ // (withBackgroundRevalidation).
209
+ if (hasNullComponents && isFillRequest(ctx.handlerContext)) return;
210
+
195
211
  const requestCtx = getRequestContext();
196
212
  if (!requestCtx) return;
197
213
 
@@ -108,6 +108,7 @@
108
108
  * 4. Render slot content if slots present
109
109
  */
110
110
  import type { MatchResult, ResolvedSegment } from "../types.js";
111
+ import { isPlaceholder } from "../types/segments.js";
111
112
  import type { MatchContext, MatchPipelineState } from "./match-context.js";
112
113
  import { debugLog } from "./logging.js";
113
114
  import { appendMetric } from "./metrics.js";
@@ -229,12 +230,23 @@ export function buildMatchResult<TEnv>(
229
230
  allIds = [...new Set(allIds)];
230
231
 
231
232
  // One pass over allSegments: keep renderable segments and collect the
232
- // handler-ran ids (resolvedIds) together.
233
+ // ids that ran (resolvedIds) together. A partial match resolves only the
234
+ // loaders it runs (revalidation.ts), so each loader segment here ran:
235
+ // the browser replaces that loader's handle pushes, and keeps those of a
236
+ // loader it skipped (handles/pushed-by.ts).
233
237
  const clientIdSet = new Set(ctx.clientSegmentIds);
234
238
  segmentsToRender = [];
235
239
  resolvedIds = [];
236
240
  for (const s of allSegments) {
237
- if (s._handlerRan) resolvedIds.push(s.id);
241
+ // A deferred loader (prefetch: false) did not run in this response:
242
+ // its pushes arrive with the fill, which lists it then.
243
+ if (s._handlerRan || (s.type === "loader" && !isPlaceholder(s))) {
244
+ resolvedIds.push(s.id);
245
+ }
246
+ // A deferred unit (prefetch: false) has a null component and is sent:
247
+ // only a segment the client does not hold is ever deferred, and
248
+ // deferral reads these same ids (prefetch-deferral.ts
249
+ // PrefetchDeferral.held), not the set the match forces renders with.
238
250
  if (
239
251
  s.component !== null ||
240
252
  s.type === "loader" ||
@@ -1,3 +1,4 @@
1
+ import { isLoaderSegmentId } from "../browser/react/filter-segment-order.js";
1
2
  import { requestHeaders } from "../server/request-headers.js";
2
3
  import type { RouteMatchResult } from "./pattern-matching.js";
3
4
 
@@ -145,7 +146,7 @@ export async function resolveNavigation(
145
146
 
146
147
  const filteredSegmentIds = clientSegmentIds.filter((id) => {
147
148
  if (id.includes(".@")) return false;
148
- if (/D\d+\./.test(id)) return false;
149
+ if (isLoaderSegmentId(id)) return false;
149
150
  return true;
150
151
  });
151
152
 
@@ -164,17 +164,19 @@ export async function resolveLoaders<TEnv>(
164
164
  // key off it.
165
165
  ...stampAwaitBeforeFlush(loaderEntry),
166
166
  loaderData: deps.wrapLoaderPromise(
167
- runInsideLoaderScope(() =>
168
- resolveLoaderData(
169
- loaderEntry,
170
- ctx,
171
- ctx.pathname,
172
- // The bake key rides for flagged loaders too: ssr:false
173
- // bakes at capture regardless of the entry's loading() lane
174
- // (loader-cache.ts capture branch) and its HIT-tail seed
175
- // overlay needs the same key.
176
- bakeLane || loaderEntry.bake === true ? segmentId : null,
177
- ),
167
+ runInsideLoaderScope(
168
+ () =>
169
+ resolveLoaderData(
170
+ loaderEntry,
171
+ ctx,
172
+ ctx.pathname,
173
+ // The bake key rides for flagged loaders too: ssr:false
174
+ // bakes at capture regardless of the entry's loading() lane
175
+ // (loader-cache.ts capture branch) and its HIT-tail seed
176
+ // overlay needs the same key.
177
+ bakeLane || loaderEntry.bake === true ? segmentId : null,
178
+ ),
179
+ loader.$$id,
178
180
  ),
179
181
  entry,
180
182
  segmentId,
@@ -216,13 +218,15 @@ export async function resolveLoaders<TEnv>(
216
218
  const { loader } = loaderEntry;
217
219
  const segmentId = `${shortCode}D${i}.${loader.$$id}`;
218
220
  const wrapped = deps.wrapLoaderPromise(
219
- runInsideLoaderScope(() =>
220
- resolveLoaderData(
221
- loaderEntry,
222
- ctx,
223
- ctx.pathname,
224
- bakeLane || loaderEntry.bake === true ? segmentId : null,
225
- ),
221
+ runInsideLoaderScope(
222
+ () =>
223
+ resolveLoaderData(
224
+ loaderEntry,
225
+ ctx,
226
+ ctx.pathname,
227
+ bakeLane || loaderEntry.bake === true ? segmentId : null,
228
+ ),
229
+ loader.$$id,
226
230
  ),
227
231
  entry,
228
232
  segmentId,
@@ -118,6 +118,7 @@ import {
118
118
  isShellCaptureActive,
119
119
  createMaskedLoaderPromise,
120
120
  } from "./loader-mask.js";
121
+ import { loaderOwner } from "./prefetch-deferral.js";
121
122
  // Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
122
123
  // import segment-resolution but never use loader caching.
123
124
  let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
@@ -658,7 +659,7 @@ function executeLoaderData<TEnv>(
658
659
  // Handle pushes: the store and the owning segment are read synchronously
659
660
  // at kickoff, as createLoaderExecutor does for the body's own pushes.
660
661
  const handleStore = _getRequestContext()?._handleStore;
661
- const owningSegmentId = internal._currentSegmentId;
662
+ const owningSegmentId = loaderOwner(ctx, loaderId);
662
663
  // Record only pushes from THIS loader's body and the loaders it awaits via
663
664
  // ctx.use: the handler and sibling loaders push into the same store
664
665
  // concurrently. A dependency can still run on a HIT for another reader
@@ -18,6 +18,7 @@ import {
18
18
  _getRequestContext,
19
19
  type RequestContext,
20
20
  } from "../../server/request-context.js";
21
+ import { isRenderableLoading } from "../../types/boundaries.js";
21
22
 
22
23
  /**
23
24
  * True when the current render is the active PPR shell capture and route loaders
@@ -38,12 +39,13 @@ export function isShellCaptureActive(
38
39
  export { createMaskedLoaderPromise } from "./mask-nested.js";
39
40
 
40
41
  /**
41
- * True when the entry's loading() is renderable (mirrors segment-system's
42
- * isRenderableLoading). Not the lane decision (lane rule: see
42
+ * True when the entry's loading() is renderable (the shared
43
+ * isRenderableLoading, types/boundaries.ts, that segment-system gates on).
44
+ * Not the lane decision (lane rule: see
43
45
  * resolveLoaderData, loader-cache.ts): callers only use it to decide whether
44
46
  * an unflagged loader's segment key rides as the bake/seed key, which is inert
45
47
  * at capture.
46
48
  */
47
49
  export function entryLoadingMasksLoaders(loading: unknown): boolean {
48
- return loading !== undefined && loading !== null && loading !== false;
50
+ return isRenderableLoading(loading);
49
51
  }