@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81
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/AGENTS.md +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +5091 -941
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +61 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +340 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +91 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +393 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +358 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +977 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { ResolvedSegment } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
mergeSegmentLoaders,
|
|
4
|
+
needsLoaderMerge,
|
|
5
|
+
insertMissingDiffSegments,
|
|
6
|
+
} from "./merge-segment-loaders.js";
|
|
7
|
+
import { assertSegmentStructure } from "./segment-structure-assert.js";
|
|
8
|
+
import { splitInterceptSegments } from "./intercept-utils.js";
|
|
9
|
+
import { debugLog } from "./logging.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Determines the merging behavior for segment reconciliation.
|
|
13
|
+
*
|
|
14
|
+
* - 'action': From server-action-bridge's own merge. Always merges loaders,
|
|
15
|
+
* always preserves cached loading (even undefined), never clears cached
|
|
16
|
+
* segment loading.
|
|
17
|
+
* - 'navigation': From partial-update during normal navigation. Does NOT merge
|
|
18
|
+
* loaders, preserves cached loading only when defined, clears truthy loading
|
|
19
|
+
* on cached segments not in server diff.
|
|
20
|
+
* - 'stale-revalidation': From partial-update during stale revalidation or
|
|
21
|
+
* action-triggered refetch. Merges loaders, always preserves cached loading
|
|
22
|
+
* (same as action), clears truthy loading on cached segments not in server diff.
|
|
23
|
+
*/
|
|
24
|
+
export type ReconcileActor = "navigation" | "action" | "stale-revalidation";
|
|
25
|
+
|
|
26
|
+
export interface ReconcileInput {
|
|
27
|
+
actor: ReconcileActor;
|
|
28
|
+
/** All segment IDs the server expects the client to have (matched array) */
|
|
29
|
+
matched: string[];
|
|
30
|
+
/** Segment IDs that changed (diff array) */
|
|
31
|
+
diff: string[];
|
|
32
|
+
/** Segments returned from server (raw array, keyed internally by ID) */
|
|
33
|
+
serverSegments: ResolvedSegment[];
|
|
34
|
+
/** Cached segments from current page (raw array, keyed internally by ID) */
|
|
35
|
+
cachedSegments: ResolvedSegment[];
|
|
36
|
+
/** When true, diff segments not in matched are inserted after their parent
|
|
37
|
+
* layout. Used during navigation when consolidation fetch returns loader
|
|
38
|
+
* segments that aren't in the matched array. */
|
|
39
|
+
insertMissingDiff?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ReconcileResult {
|
|
43
|
+
/** All merged segments in matched order (for caching and committing) */
|
|
44
|
+
segments: ResolvedSegment[];
|
|
45
|
+
/** Main segments excluding intercepts (for rendering) */
|
|
46
|
+
mainSegments: ResolvedSegment[];
|
|
47
|
+
/** Intercept segments only (passed via render options) */
|
|
48
|
+
interceptSegments: ResolvedSegment[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Single source of truth for merging server segments with cached segments.
|
|
53
|
+
*
|
|
54
|
+
* Replaces the duplicated merge loops in server-action-bridge.ts and
|
|
55
|
+
* partial-update.ts. The actor parameter controls the subtle behavioral
|
|
56
|
+
* differences between action and navigation merging:
|
|
57
|
+
*
|
|
58
|
+
* Loading preservation:
|
|
59
|
+
* - action/stale-revalidation: Always preserves cached loading value when it
|
|
60
|
+
* differs from server (even when cached is undefined). This prevents tree
|
|
61
|
+
* structure changes that would remount components and destroy useActionState
|
|
62
|
+
* during action revalidation or action-triggered refetch.
|
|
63
|
+
* - navigation: Preserves cached loading only when the cached value is defined
|
|
64
|
+
* (not undefined). When cached is undefined, lets server value through
|
|
65
|
+
* because we're building a new tree.
|
|
66
|
+
*
|
|
67
|
+
* Loader merging:
|
|
68
|
+
* - action/stale-revalidation: Merges partial loader data when server returns
|
|
69
|
+
* fewer loaders than cached (revalidation only updated some loaders).
|
|
70
|
+
* - navigation: Does not merge (full navigation fetches complete data).
|
|
71
|
+
*
|
|
72
|
+
* Cached segment handling (segments in matched but not in server response):
|
|
73
|
+
* - action: Returns cached segment as-is (preserve tree structure).
|
|
74
|
+
* - navigation/stale-revalidation: Clears truthy loading to undefined
|
|
75
|
+
* (prevents showing stale skeletons), but preserves loading=false
|
|
76
|
+
* (suppressed boundary is structural).
|
|
77
|
+
*/
|
|
78
|
+
export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
79
|
+
const { actor, matched, diff, insertMissingDiff } = input;
|
|
80
|
+
const shouldMergeLoaders = actor !== "navigation";
|
|
81
|
+
const context = actor === "action" ? "action-bridge" : "partial-update";
|
|
82
|
+
|
|
83
|
+
// Build lookup maps from arrays
|
|
84
|
+
const serverSegments = new Map<string, ResolvedSegment>();
|
|
85
|
+
input.serverSegments.forEach((s) => serverSegments.set(s.id, s));
|
|
86
|
+
const cachedSegments = new Map<string, ResolvedSegment>();
|
|
87
|
+
input.cachedSegments.forEach((s) => cachedSegments.set(s.id, s));
|
|
88
|
+
|
|
89
|
+
const diffSet = new Set(diff);
|
|
90
|
+
debugLog(
|
|
91
|
+
`[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
|
|
92
|
+
);
|
|
93
|
+
debugLog(
|
|
94
|
+
`[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
|
|
95
|
+
);
|
|
96
|
+
debugLog(
|
|
97
|
+
`[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const segments = matched
|
|
101
|
+
.map((segId: string) => {
|
|
102
|
+
const fromServer = serverSegments.get(segId);
|
|
103
|
+
const fromCache = cachedSegments.get(segId);
|
|
104
|
+
|
|
105
|
+
if (fromServer) {
|
|
106
|
+
const inDiff = diffSet.has(segId);
|
|
107
|
+
// Merge partial loader data when server returns fewer loaders than cached
|
|
108
|
+
if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
|
|
109
|
+
debugLog(
|
|
110
|
+
`[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
|
|
111
|
+
);
|
|
112
|
+
return mergeSegmentLoaders(fromServer, fromCache);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Preserve cached structural properties to maintain consistent React tree.
|
|
116
|
+
// Changing these between renders alters the element nesting
|
|
117
|
+
// (with/without RouteContentWrapper, MountContextProvider, etc.),
|
|
118
|
+
// causing React to remount components and destroy useActionState.
|
|
119
|
+
if (fromCache) {
|
|
120
|
+
let merged = fromServer;
|
|
121
|
+
|
|
122
|
+
// When server returns component: null for a layout segment, it means
|
|
123
|
+
// "this segment doesn't need re-rendering" - preserve the cached component
|
|
124
|
+
// to maintain the outlet chain and prevent React tree changes
|
|
125
|
+
if (
|
|
126
|
+
fromServer.component === null &&
|
|
127
|
+
fromServer.type === "layout" &&
|
|
128
|
+
fromCache.component != null
|
|
129
|
+
) {
|
|
130
|
+
merged = { ...merged, component: fromCache.component };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Loading preservation is actor-aware:
|
|
134
|
+
// - action/stale-revalidation: always preserve cached value to prevent
|
|
135
|
+
// tree remount (even when cached is undefined, to avoid adding a
|
|
136
|
+
// Suspense boundary that wasn't there before)
|
|
137
|
+
// - navigation: only when cached is defined (building a new tree)
|
|
138
|
+
if (actor !== "navigation") {
|
|
139
|
+
if (fromServer.loading !== fromCache.loading) {
|
|
140
|
+
merged = { ...merged, loading: fromCache.loading };
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
if (
|
|
144
|
+
fromCache.loading !== undefined &&
|
|
145
|
+
fromServer.loading !== fromCache.loading
|
|
146
|
+
) {
|
|
147
|
+
merged = { ...merged, loading: fromCache.loading };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// mountPath: SSR segments may lack mountPath while revalidated segments
|
|
152
|
+
// include it. The conditional MountContextProvider wrapper changes tree depth.
|
|
153
|
+
if (fromServer.mountPath !== fromCache.mountPath) {
|
|
154
|
+
merged = { ...merged, mountPath: fromCache.mountPath };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Dev-mode assertion: warn if the merged result still differs from cache
|
|
158
|
+
// in tree-structural properties. This catches bugs where the merge code
|
|
159
|
+
// above fails to preserve a value it should have.
|
|
160
|
+
assertSegmentStructure(fromCache, merged, context);
|
|
161
|
+
|
|
162
|
+
debugLog(
|
|
163
|
+
`[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
|
|
164
|
+
);
|
|
165
|
+
return merged;
|
|
166
|
+
}
|
|
167
|
+
debugLog(
|
|
168
|
+
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
169
|
+
);
|
|
170
|
+
return fromServer;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fall back to cached segment (server expects client to already have it)
|
|
174
|
+
if (!fromCache) {
|
|
175
|
+
if (actor === "action") {
|
|
176
|
+
console.error(`[Browser] MISSING SEGMENT: ${segId} not in cache!`);
|
|
177
|
+
} else {
|
|
178
|
+
console.warn(`[Browser] Missing segment: ${segId}`);
|
|
179
|
+
}
|
|
180
|
+
return fromCache;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
debugLog(
|
|
184
|
+
`[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Return the cached segment as-is, regardless of actor. We used to clear
|
|
188
|
+
// truthy `loading` here to prevent a stale Suspense fallback from
|
|
189
|
+
// committing against cached content, but that swapped the render tree
|
|
190
|
+
// from the LoaderBoundary branch to the plain OutletProvider branch
|
|
191
|
+
// inside renderSegments, causing React to unmount the entire chain
|
|
192
|
+
// (LoaderBoundary > Suspense > LoaderResolver > RouteContentWrapper >
|
|
193
|
+
// Suspender) every time the user opened an intercept or navigated back
|
|
194
|
+
// to a cached page. The flicker is now prevented by renderSegments'
|
|
195
|
+
// promise memoization keeping React's use() in "known fulfilled" state,
|
|
196
|
+
// so preserving `loading` keeps the element tree stable.
|
|
197
|
+
return fromCache;
|
|
198
|
+
})
|
|
199
|
+
.filter(Boolean) as ResolvedSegment[];
|
|
200
|
+
|
|
201
|
+
// Insert diff segments not in matched (e.g., loader segments from consolidation fetch).
|
|
202
|
+
// Only needed during navigation - action bridge doesn't use this.
|
|
203
|
+
if (insertMissingDiff) {
|
|
204
|
+
const matchedIdSet = new Set(matched);
|
|
205
|
+
insertMissingDiffSegments(segments, diff, matchedIdSet, serverSegments);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { main, intercept } = splitInterceptSegments(segments);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
segments,
|
|
212
|
+
mainSegments: main,
|
|
213
|
+
interceptSegments: intercept,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Reconcile error segments with cached segments.
|
|
219
|
+
*
|
|
220
|
+
* For error responses, the server returns the error boundary segment.
|
|
221
|
+
* This function overlays error segments onto the full cached tree,
|
|
222
|
+
* preserving sibling layouts that aren't in the error parent chain.
|
|
223
|
+
*/
|
|
224
|
+
export function reconcileErrorSegments(
|
|
225
|
+
cachedSegments: ResolvedSegment[],
|
|
226
|
+
errorSegments: ResolvedSegment[],
|
|
227
|
+
): ReconcileResult {
|
|
228
|
+
const errorMap = new Map<string, ResolvedSegment>();
|
|
229
|
+
errorSegments.forEach((s) => errorMap.set(s.id, s));
|
|
230
|
+
|
|
231
|
+
const segments = cachedSegments.map((cached) => {
|
|
232
|
+
const fromServer = errorMap.get(cached.id);
|
|
233
|
+
return fromServer || cached;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const { main, intercept } = splitInterceptSegments(segments);
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
segments,
|
|
240
|
+
mainSegments: main,
|
|
241
|
+
interceptSegments: intercept,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -57,6 +57,22 @@ export function assertSegmentStructure(
|
|
|
57
57
|
`The merge code should preserve the cached loading value.`,
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
// Check mountPath consistency. MountContextProvider is conditionally added
|
|
62
|
+
// in renderSegments() when mountPath is truthy, changing tree depth.
|
|
63
|
+
const cachedHasMount = !!cached.mountPath;
|
|
64
|
+
const incomingHasMount = !!incoming.mountPath;
|
|
65
|
+
if (cachedHasMount !== incomingHasMount) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[RSC Router] MountContextProvider mismatch detected in ${context} ` +
|
|
68
|
+
`for segment "${cached.id}": mountPath changed from ` +
|
|
69
|
+
`${cachedHasMount ? `"${cached.mountPath}"` : "undefined"} to ` +
|
|
70
|
+
`${incomingHasMount ? `"${incoming.mountPath}"` : "undefined"}. ` +
|
|
71
|
+
`This will cause React to remount the component, destroying ` +
|
|
72
|
+
`useActionState and other client state. ` +
|
|
73
|
+
`The merge code should preserve the cached mountPath value.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
function describeLoading(loading: unknown): string {
|