@rangojs/router 0.0.0-experimental.70 → 0.0.0-experimental.72
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/vite/index.js
CHANGED
|
@@ -1864,7 +1864,7 @@ import { resolve } from "node:path";
|
|
|
1864
1864
|
// package.json
|
|
1865
1865
|
var package_default = {
|
|
1866
1866
|
name: "@rangojs/router",
|
|
1867
|
-
version: "0.0.0-experimental.
|
|
1867
|
+
version: "0.0.0-experimental.72",
|
|
1868
1868
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1869
1869
|
keywords: [
|
|
1870
1870
|
"react",
|
|
@@ -5289,6 +5289,14 @@ async function rango(options) {
|
|
|
5289
5289
|
enforce: "pre",
|
|
5290
5290
|
config() {
|
|
5291
5291
|
return {
|
|
5292
|
+
// Wrangler/Miniflare mutates .wrangler state files (sqlite, WAL, etc.)
|
|
5293
|
+
// during normal dev operation. Those writes are not source changes,
|
|
5294
|
+
// but they can still wake Vite's watcher and create noisy HMR churn.
|
|
5295
|
+
server: {
|
|
5296
|
+
watch: {
|
|
5297
|
+
ignored: ["**/.wrangler/**"]
|
|
5298
|
+
}
|
|
5299
|
+
},
|
|
5292
5300
|
// Exclude rsc-router modules from optimization to prevent module duplication
|
|
5293
5301
|
// This ensures the same Context instance is used by both browser entry and RSC proxy modules
|
|
5294
5302
|
optimizeDeps: {
|
package/package.json
CHANGED
|
@@ -125,6 +125,69 @@ export async function collectSegments(
|
|
|
125
125
|
return segments;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Deduplicate inherited loader segments by loaderId.
|
|
130
|
+
*
|
|
131
|
+
* When a route has loaders and a child layout has parallel slots, the same
|
|
132
|
+
* loader is resolved twice: once for the route and once inherited into the
|
|
133
|
+
* layout (tagged with `_inherited`). The inherited copy is only needed when
|
|
134
|
+
* the route uses `loading()` — in that case, the loader data is inside a
|
|
135
|
+
* LoaderBoundary/Suspense that parallel slots can't reach through. Without
|
|
136
|
+
* loading(), useLoader() traverses parent contexts and finds the data.
|
|
137
|
+
*/
|
|
138
|
+
function deduplicateLoaderSegments(
|
|
139
|
+
segments: ResolvedSegment[],
|
|
140
|
+
logPrefix: string,
|
|
141
|
+
): ResolvedSegment[] {
|
|
142
|
+
// First pass: collect loaderIds of original (non-inherited) segments
|
|
143
|
+
// and whether their parent entry uses loading()
|
|
144
|
+
const originalLoaders = new Set<string>();
|
|
145
|
+
const loadersWithLoading = new Set<string>();
|
|
146
|
+
for (const s of segments) {
|
|
147
|
+
if (s.type === "loader" && s.loaderId && !s._inherited) {
|
|
148
|
+
originalLoaders.add(s.loaderId);
|
|
149
|
+
// If the segment has a sibling with loading, the parent uses loading()
|
|
150
|
+
// We detect this by checking if any non-loader segment in the same
|
|
151
|
+
// namespace has loading defined
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Check if any layout/route segment has loading — if a loader's namespace
|
|
155
|
+
// matches a segment with loading, the inherited copy is needed
|
|
156
|
+
for (const s of segments) {
|
|
157
|
+
if (s.type !== "loader" && s.loading !== undefined && s.loading !== false) {
|
|
158
|
+
// Find loaders in this namespace
|
|
159
|
+
for (const l of segments) {
|
|
160
|
+
if (l.type === "loader" && l.namespace === s.namespace && l.loaderId) {
|
|
161
|
+
loadersWithLoading.add(l.loaderId);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result: ResolvedSegment[] = [];
|
|
168
|
+
let dedupCount = 0;
|
|
169
|
+
|
|
170
|
+
for (const s of segments) {
|
|
171
|
+
if (
|
|
172
|
+
s.type === "loader" &&
|
|
173
|
+
s.loaderId &&
|
|
174
|
+
s._inherited &&
|
|
175
|
+
originalLoaders.has(s.loaderId) &&
|
|
176
|
+
!loadersWithLoading.has(s.loaderId)
|
|
177
|
+
) {
|
|
178
|
+
dedupCount++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
result.push(s);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (dedupCount > 0) {
|
|
185
|
+
debugLog(logPrefix, `deduped ${dedupCount} inherited loader segment(s)`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
128
191
|
/**
|
|
129
192
|
* Build the final MatchResult from collected segments and context
|
|
130
193
|
*/
|
|
@@ -181,6 +244,11 @@ export function buildMatchResult<TEnv>(
|
|
|
181
244
|
);
|
|
182
245
|
}
|
|
183
246
|
|
|
247
|
+
const dedupedSegments = deduplicateLoaderSegments(
|
|
248
|
+
segmentsToRender,
|
|
249
|
+
logPrefix,
|
|
250
|
+
);
|
|
251
|
+
|
|
184
252
|
debugLog(logPrefix, "all segments", {
|
|
185
253
|
segments: allSegments.map((s) => ({
|
|
186
254
|
id: s.id,
|
|
@@ -189,13 +257,23 @@ export function buildMatchResult<TEnv>(
|
|
|
189
257
|
})),
|
|
190
258
|
});
|
|
191
259
|
debugLog(logPrefix, "segments to render", {
|
|
192
|
-
segmentIds:
|
|
260
|
+
segmentIds: dedupedSegments.map((s) => s.id),
|
|
193
261
|
});
|
|
194
262
|
|
|
263
|
+
// Remove deduped loader IDs from matched so the client doesn't treat
|
|
264
|
+
// them as missing segments and trigger a fallback refetch.
|
|
265
|
+
const removedIds = new Set(
|
|
266
|
+
segmentsToRender
|
|
267
|
+
.filter((s) => !dedupedSegments.includes(s))
|
|
268
|
+
.map((s) => s.id),
|
|
269
|
+
);
|
|
270
|
+
const matchedIds =
|
|
271
|
+
removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
|
|
272
|
+
|
|
195
273
|
return {
|
|
196
|
-
segments:
|
|
197
|
-
matched:
|
|
198
|
-
diff:
|
|
274
|
+
segments: dedupedSegments,
|
|
275
|
+
matched: matchedIds,
|
|
276
|
+
diff: dedupedSegments.map((s) => s.id),
|
|
199
277
|
params: ctx.matched.params,
|
|
200
278
|
routeName: ctx.routeKey,
|
|
201
279
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
|
@@ -419,6 +419,10 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
419
419
|
deps,
|
|
420
420
|
orphan.shortCode,
|
|
421
421
|
);
|
|
422
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
423
|
+
for (const s of inheritedLoaders) {
|
|
424
|
+
s._inherited = true;
|
|
425
|
+
}
|
|
422
426
|
segments.push(...inheritedLoaders);
|
|
423
427
|
}
|
|
424
428
|
}
|
|
@@ -728,6 +732,7 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
728
732
|
for (const seg of inherited) {
|
|
729
733
|
if (!seenIds.has(seg.id)) {
|
|
730
734
|
seenIds.add(seg.id);
|
|
735
|
+
seg._inherited = true;
|
|
731
736
|
loaderSegments.push(seg);
|
|
732
737
|
}
|
|
733
738
|
}
|
|
@@ -346,6 +346,7 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
346
346
|
for (const seg of inherited.segments) {
|
|
347
347
|
if (!seenIds.has(seg.id)) {
|
|
348
348
|
seenIds.add(seg.id);
|
|
349
|
+
seg._inherited = true;
|
|
349
350
|
allLoaderSegments.push(seg);
|
|
350
351
|
}
|
|
351
352
|
}
|
|
@@ -1036,6 +1037,10 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1036
1037
|
orphan.shortCode,
|
|
1037
1038
|
stale,
|
|
1038
1039
|
);
|
|
1040
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1041
|
+
for (const s of inheritedResult.segments) {
|
|
1042
|
+
s._inherited = true;
|
|
1043
|
+
}
|
|
1039
1044
|
segments.push(...inheritedResult.segments);
|
|
1040
1045
|
matchedIds.push(...inheritedResult.matchedIds);
|
|
1041
1046
|
}
|
|
@@ -1126,6 +1131,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1126
1131
|
);
|
|
1127
1132
|
|
|
1128
1133
|
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1134
|
+
// shortCodeOverride must match the parent layout, not the parallel entry.
|
|
1129
1135
|
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1130
1136
|
parallelEntry,
|
|
1131
1137
|
context,
|
|
@@ -1138,7 +1144,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1138
1144
|
routeKey,
|
|
1139
1145
|
deps,
|
|
1140
1146
|
actionContext,
|
|
1141
|
-
|
|
1147
|
+
orphan.shortCode,
|
|
1142
1148
|
stale,
|
|
1143
1149
|
);
|
|
1144
1150
|
segments.push(...loaderResult.segments);
|
package/src/types/segments.ts
CHANGED
|
@@ -50,6 +50,7 @@ export interface ResolvedSegment {
|
|
|
50
50
|
parallelName?: string; // For parallels: the parallel group name (used to match with revalidations)
|
|
51
51
|
// Loader-specific fields
|
|
52
52
|
loaderId?: string; // For loaders: the loader $$id identifier
|
|
53
|
+
_inherited?: boolean; // For inherited loaders: dedup marker for buildMatchResult
|
|
53
54
|
loaderData?: any; // For loaders: the resolved data from loader execution
|
|
54
55
|
parallelLoading?: ReactNode; // For parallel-owned loaders: the parallel's loading fallback
|
|
55
56
|
// Intercept loader fields (for streaming loader data in parallel segments)
|
package/src/vite/rango.ts
CHANGED
|
@@ -102,6 +102,14 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
102
102
|
config() {
|
|
103
103
|
// Configure environments for cloudflare deployment
|
|
104
104
|
return {
|
|
105
|
+
// Wrangler/Miniflare mutates .wrangler state files (sqlite, WAL, etc.)
|
|
106
|
+
// during normal dev operation. Those writes are not source changes,
|
|
107
|
+
// but they can still wake Vite's watcher and create noisy HMR churn.
|
|
108
|
+
server: {
|
|
109
|
+
watch: {
|
|
110
|
+
ignored: ["**/.wrangler/**"],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
105
113
|
// Exclude rsc-router modules from optimization to prevent module duplication
|
|
106
114
|
// This ensures the same Context instance is used by both browser entry and RSC proxy modules
|
|
107
115
|
optimizeDeps: {
|