@rangojs/router 0.2.0 → 0.4.1
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 +19 -1
- package/dist/types/browser/event-controller.d.ts +4 -0
- package/dist/types/browser/link-interceptor.d.ts +17 -7
- package/dist/types/browser/navigation-bridge.d.ts +13 -1
- package/dist/types/browser/notify-listeners.d.ts +2 -0
- package/dist/types/browser/prefetch/cache.d.ts +1 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +31 -0
- package/dist/types/browser/prefetch/invalidation.d.ts +6 -0
- package/dist/types/browser/prefetch/loader.d.ts +3 -1
- package/dist/types/browser/prefetch/observer.d.ts +3 -6
- package/dist/types/browser/prefetch/runtime.d.ts +0 -1
- package/dist/types/browser/rango-state.d.ts +4 -0
- package/dist/types/browser/react/Link.d.ts +11 -19
- package/dist/types/browser/react/context.d.ts +3 -0
- package/dist/types/browser/rsc-router.d.ts +7 -2
- package/dist/types/browser/types.d.ts +6 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -3
- package/dist/types/cache/cache-scope.d.ts +82 -3
- package/dist/types/cache/cf/cf-cache-store.d.ts +2 -2
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/search-params-filter.d.ts +64 -0
- package/dist/types/cache/shell-snapshot.d.ts +4 -4
- package/dist/types/cache/types.d.ts +36 -2
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.rsc.d.ts +1 -0
- package/dist/types/router/match-middleware/cache-lookup.d.ts +13 -0
- package/dist/types/router/navigation-snapshot.d.ts +29 -0
- package/dist/types/router/prefetch-default.d.ts +28 -0
- package/dist/types/router/router-interfaces.d.ts +7 -0
- package/dist/types/router/router-options.d.ts +54 -0
- package/dist/types/rsc/capture-queue.d.ts +6 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +7 -1
- package/dist/types/rsc/shell-capture.d.ts +15 -7
- package/dist/types/rsc/shell-serve.d.ts +11 -4
- package/dist/types/rsc/types.d.ts +19 -0
- package/dist/types/server/request-context.d.ts +57 -4
- package/dist/types/testing/e2e/index.d.ts +2 -2
- package/dist/types/testing/e2e/page-helpers.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +10 -1
- package/dist/types/testing/shell-status.d.ts +6 -3
- package/dist/types/vite/plugin-types.d.ts +1 -1
- package/dist/vite/index.js +9 -6
- package/package.json +23 -22
- package/skills/caching/SKILL.md +39 -0
- package/skills/comparison/references/framework-comparison.md +9 -2
- package/skills/links/SKILL.md +24 -0
- package/skills/ppr/SKILL.md +80 -16
- package/skills/router-setup/SKILL.md +9 -0
- package/skills/testing/client-components.md +15 -14
- package/skills/vercel/SKILL.md +1 -1
- package/src/browser/event-controller.ts +110 -11
- package/src/browser/link-interceptor.ts +469 -20
- package/src/browser/navigation-bridge.ts +71 -2
- package/src/browser/navigation-store.ts +24 -1
- package/src/browser/notify-listeners.ts +22 -0
- package/src/browser/prefetch/cache.ts +4 -2
- package/src/browser/prefetch/default-strategy.ts +74 -0
- package/src/browser/prefetch/invalidation.ts +30 -0
- package/src/browser/prefetch/loader.ts +18 -17
- package/src/browser/prefetch/observer.ts +50 -22
- package/src/browser/prefetch/runtime.ts +0 -1
- package/src/browser/rango-state.ts +21 -0
- package/src/browser/react/Link.tsx +111 -101
- package/src/browser/react/NavigationProvider.tsx +1 -0
- package/src/browser/react/context.ts +4 -0
- package/src/browser/rsc-router.tsx +30 -9
- package/src/browser/types.ts +6 -0
- package/src/cache/cache-key-utils.ts +18 -4
- package/src/cache/cache-runtime.ts +7 -2
- package/src/cache/cache-scope.ts +152 -23
- package/src/cache/cf/cf-cache-store.ts +55 -16
- package/src/cache/document-cache.ts +7 -1
- package/src/cache/index.ts +7 -0
- package/src/cache/search-params-filter.ts +118 -0
- package/src/cache/shell-snapshot.ts +8 -4
- package/src/cache/types.ts +39 -2
- package/src/cache/vercel/vercel-cache-store.ts +22 -3
- package/src/index.rsc.ts +4 -0
- package/src/index.ts +6 -0
- package/src/router/match-middleware/cache-lookup.ts +146 -33
- package/src/router/match-middleware/cache-store.ts +70 -0
- package/src/router/navigation-snapshot.ts +46 -6
- package/src/router/prefetch-default.ts +59 -0
- package/src/router/router-interfaces.ts +8 -0
- package/src/router/router-options.ts +59 -1
- package/src/router.ts +7 -0
- package/src/rsc/capture-queue.ts +24 -1
- package/src/rsc/full-payload.ts +1 -0
- package/src/rsc/handler.ts +10 -0
- package/src/rsc/response-cache-serve.ts +1 -1
- package/src/rsc/rsc-rendering.ts +367 -40
- package/src/rsc/shell-build-manifest.ts +8 -1
- package/src/rsc/shell-capture.ts +99 -50
- package/src/rsc/shell-serve.ts +14 -6
- package/src/rsc/types.ts +19 -0
- package/src/server/request-context.ts +62 -3
- package/src/testing/dispatch.ts +21 -3
- package/src/testing/e2e/index.ts +6 -0
- package/src/testing/e2e/page-helpers.ts +47 -0
- package/src/testing/e2e/parity.ts +13 -0
- package/src/testing/render-route.tsx +86 -17
- package/src/testing/shell-status.ts +20 -3
- package/src/vite/plugin-types.ts +1 -1
- package/src/vite/plugins/vercel-output.ts +2 -2
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
filterSegmentOrder,
|
|
15
15
|
filterRouteSegmentIds,
|
|
16
16
|
} from "./react/filter-segment-order.js";
|
|
17
|
+
import { notifyListeners } from "./notify-listeners.js";
|
|
17
18
|
|
|
18
19
|
// Polyfill Symbol.dispose for Safari and older browsers
|
|
19
20
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -269,6 +270,71 @@ export interface EventController {
|
|
|
269
270
|
hadAnyConcurrentActions(): boolean;
|
|
270
271
|
}
|
|
271
272
|
|
|
273
|
+
type LocationChangeController = Pick<EventController, "getState" | "subscribe">;
|
|
274
|
+
|
|
275
|
+
interface LocationChangeSubscription {
|
|
276
|
+
registrations: Map<
|
|
277
|
+
symbol,
|
|
278
|
+
{ href: string; listener: (href: string) => void }
|
|
279
|
+
>;
|
|
280
|
+
notificationVersion: number;
|
|
281
|
+
unsubscribe: () => void;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const locationChangeSubscriptions = new WeakMap<
|
|
285
|
+
LocationChangeController,
|
|
286
|
+
LocationChangeSubscription
|
|
287
|
+
>();
|
|
288
|
+
|
|
289
|
+
/** Share one controller subscription across all location-change consumers. */
|
|
290
|
+
export function subscribeToLocationChange(
|
|
291
|
+
eventController: LocationChangeController,
|
|
292
|
+
listener: (href: string) => void,
|
|
293
|
+
): () => void {
|
|
294
|
+
let subscription = locationChangeSubscriptions.get(eventController);
|
|
295
|
+
if (!subscription) {
|
|
296
|
+
subscription = {
|
|
297
|
+
registrations: new Map(),
|
|
298
|
+
notificationVersion: 0,
|
|
299
|
+
unsubscribe: () => {},
|
|
300
|
+
};
|
|
301
|
+
locationChangeSubscriptions.set(eventController, subscription);
|
|
302
|
+
const currentSubscription = subscription;
|
|
303
|
+
subscription.unsubscribe = eventController.subscribe(() => {
|
|
304
|
+
const notificationVersion = ++currentSubscription.notificationVersion;
|
|
305
|
+
const nextHref = eventController.getState().location.href;
|
|
306
|
+
notifyListeners(
|
|
307
|
+
[...currentSubscription.registrations],
|
|
308
|
+
([, registration]) => {
|
|
309
|
+
if (registration.href === nextHref) return;
|
|
310
|
+
registration.href = nextHref;
|
|
311
|
+
registration.listener(nextHref);
|
|
312
|
+
},
|
|
313
|
+
([token, registration]) =>
|
|
314
|
+
currentSubscription.registrations.get(token) === registration,
|
|
315
|
+
() => notificationVersion === currentSubscription.notificationVersion,
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const token = Symbol();
|
|
321
|
+
subscription.registrations.set(token, {
|
|
322
|
+
href: eventController.getState().location.href,
|
|
323
|
+
listener,
|
|
324
|
+
});
|
|
325
|
+
let active = true;
|
|
326
|
+
return () => {
|
|
327
|
+
if (!active) return;
|
|
328
|
+
active = false;
|
|
329
|
+
subscription!.registrations.delete(token);
|
|
330
|
+
if (subscription!.registrations.size > 0) return;
|
|
331
|
+
if (locationChangeSubscriptions.get(eventController) === subscription) {
|
|
332
|
+
locationChangeSubscriptions.delete(eventController);
|
|
333
|
+
}
|
|
334
|
+
subscription!.unsubscribe();
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
272
338
|
const DEFAULT_ACTION_STATE: TrackedActionState = {
|
|
273
339
|
state: "idle",
|
|
274
340
|
actionId: null,
|
|
@@ -308,7 +374,11 @@ function makeDebouncedNotifier(listeners: Set<() => void>): () => void {
|
|
|
308
374
|
if (timeout !== null) clearTimeout(timeout);
|
|
309
375
|
timeout = setTimeout(() => {
|
|
310
376
|
timeout = null;
|
|
311
|
-
|
|
377
|
+
notifyListeners(
|
|
378
|
+
[...listeners],
|
|
379
|
+
(listener) => listener(),
|
|
380
|
+
(listener) => listeners.has(listener),
|
|
381
|
+
);
|
|
312
382
|
}, 0);
|
|
313
383
|
};
|
|
314
384
|
}
|
|
@@ -394,6 +464,39 @@ export function createEventController(
|
|
|
394
464
|
|
|
395
465
|
const actionNotifyTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
396
466
|
|
|
467
|
+
function notifyActionListenerSets(
|
|
468
|
+
subscriptions: Iterable<[string, Set<ActionStateListener>]>,
|
|
469
|
+
): void {
|
|
470
|
+
const subscriptionSnapshots = [...subscriptions].map(
|
|
471
|
+
([subscriptionId, listeners]) => ({
|
|
472
|
+
listenerSnapshot: [...listeners],
|
|
473
|
+
listeners,
|
|
474
|
+
subscriptionId,
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
function* notifications(): Generator<{
|
|
478
|
+
listener: ActionStateListener;
|
|
479
|
+
listeners: Set<ActionStateListener>;
|
|
480
|
+
state: TrackedActionState;
|
|
481
|
+
}> {
|
|
482
|
+
for (const {
|
|
483
|
+
listenerSnapshot,
|
|
484
|
+
listeners,
|
|
485
|
+
subscriptionId,
|
|
486
|
+
} of subscriptionSnapshots) {
|
|
487
|
+
const state = getActionState(subscriptionId);
|
|
488
|
+
for (const listener of listenerSnapshot) {
|
|
489
|
+
yield { listener, listeners, state };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
notifyListeners(
|
|
494
|
+
notifications(),
|
|
495
|
+
({ listener, state }) => listener(state),
|
|
496
|
+
({ listener, listeners }) => listeners.has(listener),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
397
500
|
function notifyAction(actionId: string) {
|
|
398
501
|
const existing = actionNotifyTimeouts.get(actionId);
|
|
399
502
|
if (existing !== undefined) {
|
|
@@ -403,12 +506,11 @@ export function createEventController(
|
|
|
403
506
|
actionId,
|
|
404
507
|
setTimeout(() => {
|
|
405
508
|
actionNotifyTimeouts.delete(actionId);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
509
|
+
notifyActionListenerSets(
|
|
510
|
+
[...actionListeners].filter(([subscriptionId]) =>
|
|
511
|
+
matchesActionId(subscriptionId, actionId),
|
|
512
|
+
),
|
|
513
|
+
);
|
|
412
514
|
}, 0),
|
|
413
515
|
);
|
|
414
516
|
}
|
|
@@ -846,10 +948,7 @@ export function createEventController(
|
|
|
846
948
|
// "addToCart"), not full entry actionIds. Passing them to notifyAction
|
|
847
949
|
// would fail the suffix matcher — instead, notify each subscriber with
|
|
848
950
|
// its own state.
|
|
849
|
-
|
|
850
|
-
const state = getActionState(subscriptionId);
|
|
851
|
-
listeners.forEach((listener) => listener(state));
|
|
852
|
-
}
|
|
951
|
+
notifyActionListenerSets(actionListeners);
|
|
853
952
|
}
|
|
854
953
|
|
|
855
954
|
// ========================================================================
|