@rangojs/router 0.33.1 → 0.34.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.
- package/dist/bin/{chunk-TTXC2U3G.js → chunk-4HHFQSDC.js} +10 -2
- package/dist/bin/{chunk-3B7CKBRO.js → chunk-MWOHBDPM.js} +9 -9
- package/dist/bin/{client-urls-projection-DQLWPDAT.js → client-urls-projection-4Q5EQDAL.js} +2 -2
- package/dist/bin/rango.js +1 -1
- package/dist/bin/{runtime-EPUDEHO4.js → runtime-6WLCJCN7.js} +1 -1
- package/dist/bin/{runtime-discovery-3HH6BP3K.js → runtime-discovery-2ZTYRZ3V.js} +2 -2
- package/dist/bin/{temp-rsc-server-JALTIWLT.js → temp-rsc-server-EUNKRFJD.js} +3 -3
- package/dist/testing/vitest.js +5 -0
- package/dist/types/browser/history-state.d.ts +21 -0
- package/dist/types/browser/navigation-transaction.d.ts +2 -0
- package/dist/types/client-urls/server-projection.d.ts +7 -0
- package/dist/types/client-urls/types.d.ts +13 -14
- package/dist/types/errors.d.ts +5 -0
- package/dist/types/loader-store.d.ts +11 -1
- package/dist/types/prefetch-markers.d.ts +13 -4
- package/dist/types/route-definition/helpers-types.d.ts +14 -10
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +39 -21
- package/dist/types/server/context.d.ts +6 -5
- package/dist/types/testing/render-route.d.ts +21 -1
- package/dist/types/testing/serve-shell-request.d.ts +7 -7
- package/dist/types/testing/vitest-stubs/plugin-rsc-browser.d.ts +33 -0
- package/dist/types/types/loader-types.d.ts +10 -8
- package/dist/types/urls/path-helper-types.d.ts +9 -8
- package/dist/vite/index.js +30 -13
- package/package.json +1 -1
- package/skills/app-structure/large-app.md +3 -0
- package/skills/client-urls/SKILL.md +13 -10
- package/skills/hooks/data.md +5 -0
- package/skills/hooks/state.md +13 -11
- package/skills/intercept/SKILL.md +8 -4
- package/skills/links/SKILL.md +15 -12
- package/skills/loader/SKILL.md +52 -39
- package/skills/ppr/SKILL.md +1 -1
- package/skills/route/SKILL.md +15 -9
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +67 -19
- package/skills/testing/serve-shell-request.md +19 -7
- package/skills/ux-architecture/SKILL.md +16 -11
- package/src/browser/history-state.ts +48 -0
- package/src/browser/navigation-bridge.ts +12 -1
- package/src/browser/navigation-transaction.ts +14 -6
- package/src/browser/partial-update.ts +12 -4
- package/src/browser/prefetch/fetch.ts +3 -3
- package/src/client-urls/client-urls.ts +11 -7
- package/src/client-urls/server-projection.ts +21 -3
- package/src/client-urls/types.ts +13 -14
- package/src/errors.ts +17 -0
- package/src/loader-store.ts +41 -4
- package/src/prefetch-markers.ts +18 -4
- package/src/route-definition/dsl-helpers.ts +13 -6
- package/src/route-definition/helpers-types.ts +14 -10
- package/src/router/loader-resolution.ts +4 -4
- package/src/router/match-api.ts +1 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +108 -50
- package/src/rsc/rsc-rendering.ts +1 -1
- package/src/server/context.ts +6 -5
- package/src/testing/render-route.tsx +122 -7
- package/src/testing/serve-shell-request.ts +7 -7
- package/src/testing/vitest-stubs/plugin-rsc-browser.ts +61 -0
- package/src/testing/vitest.ts +5 -0
- package/src/types/loader-types.ts +10 -8
- package/src/urls/path-helper-types.ts +9 -8
- package/src/use-loader.tsx +29 -7
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
DefaultVars,
|
|
12
12
|
} from "./global-namespace.js";
|
|
13
13
|
import type { RequestScope } from "./request-scope.js";
|
|
14
|
+
import type { PrefetchOption } from "../prefetch-markers.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Context passed to loader functions during execution
|
|
@@ -207,13 +208,14 @@ export type LoaderFn<
|
|
|
207
208
|
export type LoaderOptions = {
|
|
208
209
|
ssr?: boolean;
|
|
209
210
|
/**
|
|
210
|
-
* `prefetch: false` keeps this loader out of
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* (`X-Rango-Prefetch:
|
|
215
|
-
*
|
|
216
|
-
*
|
|
211
|
+
* `prefetch: false` keeps this loader out of every `<Link>` prefetch: a
|
|
212
|
+
* prefetch (a partial request with `X-Rango-Prefetch`) does not execute
|
|
213
|
+
* it; the payload marks it deferred. `prefetch: "intent"` keeps it out of
|
|
214
|
+
* speculative prefetches only: a viewport or render prefetch
|
|
215
|
+
* (`X-Rango-Prefetch: 1`) defers it, and a hover prefetch or
|
|
216
|
+
* `useRouter().prefetch()` (`X-Rango-Prefetch: intent`) runs it, so a click
|
|
217
|
+
* after a hover needs no follow-up request for it. A document request and
|
|
218
|
+
* a navigation with no prefetch to adopt run it as usual.
|
|
217
219
|
*
|
|
218
220
|
* A deferred loader never blocks the navigation that adopts the prefetch:
|
|
219
221
|
* it behaves like a loader that is still streaming. Its own `<Suspense>`
|
|
@@ -238,7 +240,7 @@ export type LoaderOptions = {
|
|
|
238
240
|
* request only deferred work and middleware run, so values a held handler
|
|
239
241
|
* would have set with `ctx.set()` are not visible.
|
|
240
242
|
*/
|
|
241
|
-
prefetch?:
|
|
243
|
+
prefetch?: PrefetchOption;
|
|
242
244
|
};
|
|
243
245
|
|
|
244
246
|
/**
|
|
@@ -58,6 +58,7 @@ import type {
|
|
|
58
58
|
} from "./pattern-types.js";
|
|
59
59
|
import type { ExtractRoutes, ExtractResponses } from "./type-extraction.js";
|
|
60
60
|
import type { ClientUrlPatterns } from "../client-urls/types.js";
|
|
61
|
+
import type { PrefetchOption } from "../prefetch-markers.js";
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* Base path function signature for defining routes with URL patterns.
|
|
@@ -364,10 +365,10 @@ export type PathHelpers<TEnv> = {
|
|
|
364
365
|
* freezes into the shell (nested promises stay live holes) — the pre-flush
|
|
365
366
|
* promise applied to the prelude.
|
|
366
367
|
*
|
|
367
|
-
* Pass `{ prefetch: false }` to keep the loader out of
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
368
|
+
* Pass `{ prefetch: false }` to keep the loader out of prefetches: a
|
|
369
|
+
* prefetch skips it and the click that adopts it fetches it in one
|
|
370
|
+
* follow-up request. `{ prefetch: "intent" }` defers it in viewport and
|
|
371
|
+
* render prefetches only (see {@link LoaderOptions}). Only on a segment the
|
|
371
372
|
* client does not have yet: a loader the client already holds runs in the
|
|
372
373
|
* prefetch when it revalidates.
|
|
373
374
|
*/
|
|
@@ -381,9 +382,9 @@ export type PathHelpers<TEnv> = {
|
|
|
381
382
|
* Attach a loading component to the current route/layout.
|
|
382
383
|
*
|
|
383
384
|
* `{ ssr: false }` skips the fallback on document requests. `{ prefetch:
|
|
384
|
-
* false }` keeps what the fallback covers out of
|
|
385
|
-
* prefetches
|
|
386
|
-
* to the client: the entry's handler, its loaders,
|
|
385
|
+
* false }` keeps what the fallback covers out of prefetches (`"intent"`:
|
|
386
|
+
* viewport and render prefetches only, see {@link LoaderOptions}), when this
|
|
387
|
+
* entry's segment is new to the client: the entry's handler, its loaders,
|
|
387
388
|
* its orphan layouts and parallel slots and, for a layout, every deeper
|
|
388
389
|
* entry. The click that adopts the prefetch shows the fallback and fetches
|
|
389
390
|
* them in one follow-up request, which reads cache() and "use cache" as
|
|
@@ -397,7 +398,7 @@ export type PathHelpers<TEnv> = {
|
|
|
397
398
|
*/
|
|
398
399
|
loading: (
|
|
399
400
|
component: ReactNode | (() => ReactNode),
|
|
400
|
-
options?: { ssr?: boolean; prefetch?:
|
|
401
|
+
options?: { ssr?: boolean; prefetch?: PrefetchOption },
|
|
401
402
|
) => LoadingItem;
|
|
402
403
|
|
|
403
404
|
/**
|
package/src/use-loader.tsx
CHANGED
|
@@ -270,10 +270,15 @@ function useLoaderInternal<T>(
|
|
|
270
270
|
const loaders: LoaderStore | undefined = useContext(NavigationStoreContext)
|
|
271
271
|
?.store?.loaders;
|
|
272
272
|
|
|
273
|
+
// Committed route data (updated by the reset effect below), read by the
|
|
274
|
+
// store when a refresh begins: the snapshot it produces belongs to it.
|
|
275
|
+
const prevContextDataRef = useRef(contextData);
|
|
276
|
+
const routeDataOf = useCallback(() => prevContextDataRef.current, []);
|
|
277
|
+
|
|
273
278
|
const groupRefetch = useCallback(async (): Promise<void> => {
|
|
274
279
|
if (!loaderId || !loaders) return;
|
|
275
280
|
const requestId = loaders.reserveRequestId(bucketKey);
|
|
276
|
-
loaders.beginRequest(bucketKey, requestId);
|
|
281
|
+
loaders.beginRequest(bucketKey, requestId, routeDataOf);
|
|
277
282
|
try {
|
|
278
283
|
const url = new URL(window.location.href);
|
|
279
284
|
url.searchParams.set("_rsc_loader", loaderId);
|
|
@@ -316,6 +321,15 @@ function useLoaderInternal<T>(
|
|
|
316
321
|
sharedState.bucketKey === bucketKey
|
|
317
322
|
? sharedState.snapshot
|
|
318
323
|
: EMPTY_LOADER_SNAPSHOT;
|
|
324
|
+
// A route reader shows a refresh only when it was requested for this
|
|
325
|
+
// reader's route data: never another page's, nor one a navigation replaced
|
|
326
|
+
// (the rule the reset effect below applies when a mounted reader's data
|
|
327
|
+
// changes, here also for a reader that mounts).
|
|
328
|
+
const sharedValue =
|
|
329
|
+
sharedSnapshot.hasValue &&
|
|
330
|
+
(!hasContextData || sharedSnapshot.valueFor.has(contextData));
|
|
331
|
+
const sharedRequest =
|
|
332
|
+
!hasContextData || sharedSnapshot.requestFor.has(contextData);
|
|
319
333
|
useEffect(() => {
|
|
320
334
|
if (!loaders) return;
|
|
321
335
|
const initial = loaders.getSnapshot(bucketKey);
|
|
@@ -343,6 +357,7 @@ function useLoaderInternal<T>(
|
|
|
343
357
|
onStreamPending: hasContextData
|
|
344
358
|
? () => startTransition(() => setStreamPending(true))
|
|
345
359
|
: undefined,
|
|
360
|
+
routeData: hasContextData ? routeDataOf : undefined,
|
|
346
361
|
},
|
|
347
362
|
);
|
|
348
363
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional:
|
|
@@ -388,7 +403,6 @@ function useLoaderInternal<T>(
|
|
|
388
403
|
// any pre-navigation load() promise that resolves later fails its gate
|
|
389
404
|
// and is dropped — fixes the race where a stale fetch overwrites the
|
|
390
405
|
// new route's context.
|
|
391
|
-
const prevContextDataRef = useRef(contextData);
|
|
392
406
|
useEffect(() => {
|
|
393
407
|
if (prevContextDataRef.current !== contextData) {
|
|
394
408
|
setLocalFetchedData({ has: false, value: undefined });
|
|
@@ -407,11 +421,14 @@ function useLoaderInternal<T>(
|
|
|
407
421
|
// `has`/`hasValue` gate each level so a committed falsy value is not skipped.
|
|
408
422
|
const data = localFetchedData.has
|
|
409
423
|
? localFetchedData.value
|
|
410
|
-
:
|
|
424
|
+
: sharedValue
|
|
411
425
|
? (sharedSnapshot.value as T | undefined)
|
|
412
426
|
: contextData;
|
|
413
|
-
const isLoading =
|
|
414
|
-
|
|
427
|
+
const isLoading =
|
|
428
|
+
localIsLoading ||
|
|
429
|
+
(sharedRequest && sharedSnapshot.isLoading) ||
|
|
430
|
+
streamPending;
|
|
431
|
+
const error = localError ?? (sharedRequest ? sharedSnapshot.error : null);
|
|
415
432
|
|
|
416
433
|
const throwOnError = options?.throwOnError ?? true;
|
|
417
434
|
|
|
@@ -446,7 +463,11 @@ function useLoaderInternal<T>(
|
|
|
446
463
|
if (shared) {
|
|
447
464
|
sharedRequestId = loaders.reserveRequestId(bucket);
|
|
448
465
|
lastSharedRequestIdRef.current = sharedRequestId;
|
|
449
|
-
loaders.beginRequest(
|
|
466
|
+
loaders.beginRequest(
|
|
467
|
+
bucket,
|
|
468
|
+
sharedRequestId,
|
|
469
|
+
hasContextDataRef.current ? routeDataOf : undefined,
|
|
470
|
+
);
|
|
450
471
|
} else {
|
|
451
472
|
localRequestId = ++localRequestIdRef.current;
|
|
452
473
|
setLocalIsLoading(true);
|
|
@@ -559,12 +580,13 @@ function useLoaderInternal<T>(
|
|
|
559
580
|
}
|
|
560
581
|
}
|
|
561
582
|
},
|
|
562
|
-
[throwOnError, loaders],
|
|
583
|
+
[throwOnError, loaders, routeDataOf],
|
|
563
584
|
);
|
|
564
585
|
|
|
565
586
|
if (throwOnError) {
|
|
566
587
|
if (localError) throw localError;
|
|
567
588
|
if (
|
|
589
|
+
sharedRequest &&
|
|
568
590
|
sharedSnapshot.error &&
|
|
569
591
|
lastSharedRequestIdRef.current !== null &&
|
|
570
592
|
sharedSnapshot.requestId === lastSharedRequestIdRef.current
|