@rangojs/router 0.0.0-experimental.88a3b2f7 → 0.0.0-experimental.8bd1b239

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 (52) hide show
  1. package/dist/vite/index.js +307 -128
  2. package/package.json +3 -3
  3. package/skills/handler-use/SKILL.md +362 -0
  4. package/skills/intercept/SKILL.md +20 -0
  5. package/skills/layout/SKILL.md +22 -0
  6. package/skills/middleware/SKILL.md +32 -3
  7. package/skills/migrate-nextjs/SKILL.md +560 -0
  8. package/skills/migrate-react-router/SKILL.md +764 -0
  9. package/skills/parallel/SKILL.md +59 -0
  10. package/skills/rango/SKILL.md +24 -22
  11. package/skills/route/SKILL.md +24 -0
  12. package/src/browser/navigation-bridge.ts +21 -2
  13. package/src/browser/navigation-client.ts +34 -6
  14. package/src/browser/partial-update.ts +14 -2
  15. package/src/browser/prefetch/cache.ts +16 -6
  16. package/src/browser/prefetch/fetch.ts +60 -4
  17. package/src/browser/react/Link.tsx +25 -2
  18. package/src/browser/react/use-navigation.ts +11 -10
  19. package/src/browser/segment-reconciler.ts +36 -14
  20. package/src/build/route-trie.ts +50 -24
  21. package/src/client.tsx +84 -230
  22. package/src/index.ts +41 -9
  23. package/src/reverse.ts +4 -1
  24. package/src/route-definition/dsl-helpers.ts +164 -21
  25. package/src/route-definition/helpers-types.ts +61 -14
  26. package/src/router/handler-context.ts +4 -1
  27. package/src/router/loader-resolution.ts +70 -46
  28. package/src/router/match-middleware/cache-lookup.ts +10 -5
  29. package/src/router/match-middleware/segment-resolution.ts +1 -1
  30. package/src/router/match-result.ts +82 -4
  31. package/src/router/segment-resolution/fresh.ts +52 -0
  32. package/src/router/segment-resolution/revalidation.ts +69 -1
  33. package/src/rsc/handler.ts +13 -5
  34. package/src/rsc/loader-fetch.ts +23 -3
  35. package/src/rsc/progressive-enhancement.ts +10 -2
  36. package/src/rsc/rsc-rendering.ts +5 -1
  37. package/src/rsc/server-action.ts +6 -0
  38. package/src/rsc/types.ts +1 -0
  39. package/src/segment-content-promise.ts +67 -0
  40. package/src/segment-loader-promise.ts +122 -0
  41. package/src/segment-system.tsx +11 -61
  42. package/src/server/handle-store.ts +19 -0
  43. package/src/server/request-context.ts +54 -14
  44. package/src/types/segments.ts +1 -1
  45. package/src/urls/path-helper-types.ts +34 -5
  46. package/src/use-loader.tsx +77 -5
  47. package/src/vite/discovery/prerender-collection.ts +124 -83
  48. package/src/vite/plugins/expose-id-utils.ts +12 -0
  49. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  50. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  51. package/src/vite/router-discovery.ts +25 -3
  52. package/src/vite/utils/prerender-utils.ts +20 -6
@@ -18,6 +18,9 @@ function hashId(filePath, exportName) {
18
18
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
19
  return `${hash.slice(0, 8)}#${exportName}`;
20
20
  }
21
+ function makeStubId(filePath, exportName, isBuild) {
22
+ return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
+ }
21
24
  function hashInlineId(filePath, lineNumber, index) {
22
25
  const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
23
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
@@ -910,9 +913,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
910
913
  });
911
914
  return { code: stubs.join("\n") + "\n", map: null };
912
915
  }
913
- function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
914
- if (bindings.length === 0) return null;
915
- const s = new MagicString2(code);
916
+ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
916
917
  let hasChanges = false;
917
918
  for (const binding of bindings) {
918
919
  const exportName = binding.exportNames[0];
@@ -924,15 +925,7 @@ function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
924
925
  );
925
926
  hasChanges = true;
926
927
  }
927
- if (!hasChanges) return null;
928
- return {
929
- code: s.toString(),
930
- map: s.generateMap({
931
- source: sourceId,
932
- includeContent: true,
933
- hires: "boundary"
934
- })
935
- };
928
+ return hasChanges;
936
929
  }
937
930
  function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
938
931
  let hasChanges = false;
@@ -1269,15 +1262,6 @@ ${lazyImports.join(",\n")}
1269
1262
  isBuild
1270
1263
  );
1271
1264
  if (wholeFile) return wholeFile;
1272
- const exprStubs = generateExprStubs(
1273
- PRERENDER_CONFIG,
1274
- bindings,
1275
- code,
1276
- filePath,
1277
- id,
1278
- isBuild
1279
- );
1280
- if (exprStubs) return exprStubs;
1281
1265
  }
1282
1266
  if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1283
1267
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
@@ -1329,15 +1313,134 @@ ${lazyImports.join(",\n")}
1329
1313
  isBuild
1330
1314
  );
1331
1315
  if (wholeFile) return wholeFile;
1332
- const exprStubs = generateExprStubs(
1333
- STATIC_CONFIG,
1334
- bindings,
1335
- code,
1336
- filePath,
1337
- id,
1338
- isBuild
1339
- );
1340
- if (exprStubs) return exprStubs;
1316
+ }
1317
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1318
+ const prerenderFnNames = hasPrerenderHandlerCode ? getFnNames(PRERENDER_CONFIG.fnName) : [];
1319
+ const staticFnNames = hasStaticHandlerCode ? getFnNames(STATIC_CONFIG.fnName) : [];
1320
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
1321
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
1322
+ const lsFnNames = hasLocationStateCode ? getFnNames("createLocationState") : [];
1323
+ const allBindings = [];
1324
+ for (const fnNames of [
1325
+ prerenderFnNames,
1326
+ staticFnNames,
1327
+ loaderFnNames,
1328
+ handleFnNames,
1329
+ lsFnNames
1330
+ ]) {
1331
+ if (fnNames.length > 0) {
1332
+ allBindings.push(...getBindings(code, fnNames));
1333
+ }
1334
+ }
1335
+ let canStubWholeFile = allBindings.length > 0 && isExportOnlyFile(code, allBindings);
1336
+ if (canStubWholeFile && (handleFnNames.length > 0 || lsFnNames.length > 0)) {
1337
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
1338
+ const strippedBindings = [];
1339
+ const localDeclPattern = /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
1340
+ let declMatch;
1341
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
1342
+ const name = declMatch[1];
1343
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
1344
+ strippedBindings.push(name);
1345
+ }
1346
+ }
1347
+ const importPattern = /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1348
+ let importMatch;
1349
+ while ((importMatch = importPattern.exec(code)) !== null) {
1350
+ for (const spec of importMatch[1].split(",")) {
1351
+ const m = spec.trim().match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
1352
+ if (m) strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
1353
+ }
1354
+ }
1355
+ const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1356
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
1357
+ strippedBindings.push(importMatch[1]);
1358
+ }
1359
+ const nsImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1360
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
1361
+ strippedBindings.push(importMatch[1]);
1362
+ }
1363
+ if (strippedBindings.length > 0) {
1364
+ const preservedBindings = allBindings.filter((b) => {
1365
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
1366
+ return handleFnNames.some((n) => fc.includes(n)) || lsFnNames.some((n) => fc.includes(n));
1367
+ });
1368
+ const strippedRe = new RegExp(
1369
+ `\\b(?:${strippedBindings.join("|")})\\b`
1370
+ );
1371
+ canStubWholeFile = !preservedBindings.some((b) => {
1372
+ const expr = code.slice(b.callExprStart, b.callCloseParenPos + 1);
1373
+ return strippedRe.test(expr);
1374
+ });
1375
+ }
1376
+ }
1377
+ if (canStubWholeFile) {
1378
+ const lines = [];
1379
+ const neededImports = [];
1380
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
1381
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
1382
+ if (neededImports.length > 0) {
1383
+ lines.push(
1384
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`
1385
+ );
1386
+ }
1387
+ for (const binding of allBindings) {
1388
+ const fnCall = code.slice(
1389
+ binding.callExprStart,
1390
+ binding.callOpenParenPos + 1
1391
+ );
1392
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
1393
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
1394
+ const primaryName = binding.exportNames[0];
1395
+ const stubId = makeStubId(filePath, primaryName, isBuild);
1396
+ if (isHandle || isLocationState) {
1397
+ const rawArgs = code.slice(binding.callOpenParenPos + 1, binding.callCloseParenPos).replace(/\b_c\d*\s*=\s*/g, "");
1398
+ const canonicalName = isHandle ? "createHandle" : "createLocationState";
1399
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
1400
+ let rawCallee = code.slice(
1401
+ binding.callExprStart,
1402
+ binding.callOpenParenPos
1403
+ );
1404
+ for (const alias of activeFnNames) {
1405
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
1406
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
1407
+ break;
1408
+ }
1409
+ }
1410
+ if (isHandle) {
1411
+ const idParam = binding.argCount === 0 ? `undefined, "${stubId}"` : `, "${stubId}"`;
1412
+ lines.push(
1413
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`
1414
+ );
1415
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
1416
+ } else {
1417
+ lines.push(
1418
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`
1419
+ );
1420
+ lines.push(
1421
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`
1422
+ );
1423
+ }
1424
+ for (const name of binding.exportNames.slice(1)) {
1425
+ lines.push(`export const ${name} = ${primaryName};`);
1426
+ }
1427
+ } else {
1428
+ let brand = "loader";
1429
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
1430
+ brand = PRERENDER_CONFIG.brand;
1431
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
1432
+ brand = STATIC_CONFIG.brand;
1433
+ }
1434
+ lines.push(
1435
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`
1436
+ );
1437
+ for (const name of binding.exportNames.slice(1)) {
1438
+ lines.push(`export const ${name} = ${primaryName};`);
1439
+ }
1440
+ }
1441
+ }
1442
+ return { code: lines.join("\n") + "\n", map: null };
1443
+ }
1341
1444
  }
1342
1445
  if (hasStaticHandlerCode && isRscEnv && isBuild) {
1343
1446
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
@@ -1372,25 +1475,41 @@ ${lazyImports.join(",\n")}
1372
1475
  isBuild
1373
1476
  ) || changed;
1374
1477
  }
1375
- if (hasPrerenderHandlerCode && isRscEnv) {
1478
+ if (hasPrerenderHandlerCode) {
1376
1479
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1377
- changed = transformHandlerIds(
1378
- PRERENDER_CONFIG,
1379
- getBindings(code, fnNames),
1380
- s,
1381
- filePath,
1382
- isBuild
1383
- ) || changed;
1480
+ const bindings = getBindings(code, fnNames);
1481
+ if (isRscEnv) {
1482
+ changed = transformHandlerIds(
1483
+ PRERENDER_CONFIG,
1484
+ bindings,
1485
+ s,
1486
+ filePath,
1487
+ isBuild
1488
+ ) || changed;
1489
+ } else {
1490
+ changed = stubHandlerExprs(
1491
+ PRERENDER_CONFIG,
1492
+ bindings,
1493
+ s,
1494
+ filePath,
1495
+ isBuild
1496
+ ) || changed;
1497
+ }
1384
1498
  }
1385
- if (hasStaticHandlerCode && isRscEnv) {
1499
+ if (hasStaticHandlerCode) {
1386
1500
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
1387
- changed = transformHandlerIds(
1388
- STATIC_CONFIG,
1389
- getBindings(code, fnNames),
1390
- s,
1391
- filePath,
1392
- isBuild
1393
- ) || changed;
1501
+ const bindings = getBindings(code, fnNames);
1502
+ if (isRscEnv) {
1503
+ changed = transformHandlerIds(
1504
+ STATIC_CONFIG,
1505
+ bindings,
1506
+ s,
1507
+ filePath,
1508
+ isBuild
1509
+ ) || changed;
1510
+ } else {
1511
+ changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1512
+ }
1394
1513
  }
1395
1514
  if (!changed) return;
1396
1515
  return {
@@ -1745,7 +1864,7 @@ import { resolve } from "node:path";
1745
1864
  // package.json
1746
1865
  var package_default = {
1747
1866
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.88a3b2f7",
1867
+ version: "0.0.0-experimental.8bd1b239",
1749
1868
  description: "Django-inspired RSC router with composable URL patterns",
1750
1869
  keywords: [
1751
1870
  "react",
@@ -1887,7 +2006,7 @@ var package_default = {
1887
2006
  "test:unit:watch": "vitest"
1888
2007
  },
1889
2008
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.19",
2009
+ "@vitejs/plugin-rsc": "^0.5.23",
1891
2010
  "magic-string": "^0.30.17",
1892
2011
  picomatch: "^4.0.3",
1893
2012
  "rsc-html-stream": "^0.0.7"
@@ -1907,7 +2026,7 @@ var package_default = {
1907
2026
  },
1908
2027
  peerDependencies: {
1909
2028
  "@cloudflare/vite-plugin": "^1.25.0",
1910
- "@vitejs/plugin-rsc": "^0.5.14",
2029
+ "@vitejs/plugin-rsc": "^0.5.23",
1911
2030
  react: "^18.0.0 || ^19.0.0",
1912
2031
  vite: "^7.3.0"
1913
2032
  },
@@ -3461,11 +3580,19 @@ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3461
3580
  let hadOmittedOptional = false;
3462
3581
  for (const [key, value] of Object.entries(params)) {
3463
3582
  const escaped = escapeRegExp2(key);
3464
- result = result.replace(
3465
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3466
- encode(value)
3467
- );
3468
- result = result.replace(`*${key}`, encode(value));
3583
+ if (value === "") {
3584
+ result = result.replace(
3585
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3586
+ ""
3587
+ );
3588
+ result = result.replace(`*${key}`, "");
3589
+ } else {
3590
+ result = result.replace(
3591
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3592
+ encode(value)
3593
+ );
3594
+ result = result.replace(`*${key}`, encode(value));
3595
+ }
3469
3596
  }
3470
3597
  result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3471
3598
  hadOmittedOptional = true;
@@ -3584,93 +3711,126 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3584
3711
  if (!params) return pattern;
3585
3712
  return substituteRouteParams(pattern, params);
3586
3713
  };
3714
+ let resolvedRoutes = 0;
3715
+ let totalDynamic = 0;
3587
3716
  for (const { manifest } of allManifests) {
3588
3717
  if (!manifest.prerenderRoutes) continue;
3589
- const defs = manifest._prerenderDefs || {};
3590
- const passthroughSet = new Set(manifest.passthroughRoutes || []);
3591
3718
  for (const routeName of manifest.prerenderRoutes) {
3592
3719
  const pattern = manifest.routeManifest[routeName];
3593
- if (!pattern) continue;
3594
- const def = defs[routeName];
3595
- const isPassthroughRoute = passthroughSet.has(routeName);
3596
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3597
- if (!hasDynamic) {
3598
- entries.push({
3599
- urlPath: pattern.replace(/\/$/, "") || "/",
3600
- routeName,
3601
- concurrency: 1,
3602
- isPassthroughRoute
3603
- });
3604
- } else {
3605
- if (def?.getParams) {
3606
- try {
3607
- const buildVars = {};
3608
- const buildEnv = state.resolvedBuildEnv;
3609
- const getParamsCtx = {
3610
- build: true,
3611
- dev: !state.isBuildMode,
3612
- set: ((keyOrVar, value) => {
3613
- contextSet(buildVars, keyOrVar, value);
3614
- }),
3615
- reverse: getParamsReverse,
3616
- get env() {
3617
- if (buildEnv !== void 0) return buildEnv;
3618
- throw new Error(
3619
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3720
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
3721
+ totalDynamic++;
3722
+ }
3723
+ }
3724
+ }
3725
+ const paramsStart = performance.now();
3726
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
3727
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3728
+ console.log(
3729
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3730
+ );
3731
+ }, 5e3) : void 0;
3732
+ try {
3733
+ for (const { manifest } of allManifests) {
3734
+ if (!manifest.prerenderRoutes) continue;
3735
+ const defs = manifest._prerenderDefs || {};
3736
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3737
+ for (const routeName of manifest.prerenderRoutes) {
3738
+ const pattern = manifest.routeManifest[routeName];
3739
+ if (!pattern) continue;
3740
+ const def = defs[routeName];
3741
+ const isPassthroughRoute = passthroughSet.has(routeName);
3742
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
3743
+ if (!hasDynamic) {
3744
+ entries.push({
3745
+ urlPath: pattern.replace(/\/$/, "") || "/",
3746
+ routeName,
3747
+ concurrency: 1,
3748
+ isPassthroughRoute
3749
+ });
3750
+ } else {
3751
+ if (def?.getParams) {
3752
+ try {
3753
+ const buildVars = {};
3754
+ const buildEnv = state.resolvedBuildEnv;
3755
+ const getParamsCtx = {
3756
+ build: true,
3757
+ dev: !state.isBuildMode,
3758
+ set: ((keyOrVar, value) => {
3759
+ contextSet(buildVars, keyOrVar, value);
3760
+ }),
3761
+ reverse: getParamsReverse,
3762
+ get env() {
3763
+ if (buildEnv !== void 0) return buildEnv;
3764
+ throw new Error(
3765
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3766
+ );
3767
+ }
3768
+ };
3769
+ const paramsList = await def.getParams(getParamsCtx);
3770
+ const concurrency = def.options?.concurrency ?? 1;
3771
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3772
+ for (const params of paramsList) {
3773
+ let url = substituteRouteParams(
3774
+ pattern,
3775
+ params,
3776
+ encodePathParam
3620
3777
  );
3621
- }
3622
- };
3623
- const paramsList = await def.getParams(getParamsCtx);
3624
- const concurrency = def.options?.concurrency ?? 1;
3625
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3626
- for (const params of paramsList) {
3627
- let url = substituteRouteParams(
3628
- pattern,
3629
- params,
3630
- encodePathParam
3631
- );
3632
- if (url.includes("*")) {
3633
- const wildcardValue = params["*"] ?? params.splat;
3634
- if (wildcardValue !== void 0) {
3635
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
3778
+ if (url.includes("*")) {
3779
+ const wildcardValue = params["*"] ?? params.splat;
3780
+ if (wildcardValue !== void 0) {
3781
+ url = url.replace(
3782
+ /\*[^/]*$/,
3783
+ encodePathParam(wildcardValue)
3784
+ );
3785
+ }
3636
3786
  }
3787
+ entries.push({
3788
+ urlPath: url.replace(/\/$/, "") || "/",
3789
+ routeName,
3790
+ concurrency,
3791
+ ...hasBuildVars ? { buildVars } : {},
3792
+ isPassthroughRoute
3793
+ });
3637
3794
  }
3638
- entries.push({
3639
- urlPath: url.replace(/\/$/, "") || "/",
3640
- routeName,
3641
- concurrency,
3642
- ...hasBuildVars ? { buildVars } : {},
3643
- isPassthroughRoute
3644
- });
3645
- }
3646
- } catch (err) {
3647
- if (err.name === "Skip") {
3648
- console.log(
3649
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3650
- );
3651
- notifyOnError(
3652
- registry,
3653
- err,
3654
- "prerender",
3655
- routeName,
3656
- void 0,
3657
- true
3795
+ resolvedRoutes++;
3796
+ } catch (err) {
3797
+ resolvedRoutes++;
3798
+ if (err.name === "Skip") {
3799
+ console.log(
3800
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3801
+ );
3802
+ notifyOnError(
3803
+ registry,
3804
+ err,
3805
+ "prerender",
3806
+ routeName,
3807
+ void 0,
3808
+ true
3809
+ );
3810
+ continue;
3811
+ }
3812
+ console.error(
3813
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3658
3814
  );
3659
- continue;
3815
+ notifyOnError(registry, err, "prerender", routeName);
3816
+ throw err;
3660
3817
  }
3661
- console.error(
3662
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3818
+ } else {
3819
+ console.warn(
3820
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3663
3821
  );
3664
- notifyOnError(registry, err, "prerender", routeName);
3665
- throw err;
3666
3822
  }
3667
- } else {
3668
- console.warn(
3669
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3670
- );
3671
3823
  }
3672
3824
  }
3673
3825
  }
3826
+ } finally {
3827
+ if (progressInterval) {
3828
+ clearInterval(progressInterval);
3829
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3830
+ console.log(
3831
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3832
+ );
3833
+ }
3674
3834
  }
3675
3835
  if (entries.length === 0) return;
3676
3836
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
@@ -4785,7 +4945,26 @@ ${err.stack}`
4785
4945
  res.end("Missing pathname");
4786
4946
  return;
4787
4947
  }
4788
- let registry = mainRegistry;
4948
+ const rscEnv = server.environments?.rsc;
4949
+ let registry = null;
4950
+ if (rscEnv?.runner && s.resolvedEntryPath) {
4951
+ try {
4952
+ await rscEnv.runner.import(s.resolvedEntryPath);
4953
+ const serverMod = await rscEnv.runner.import(
4954
+ "@rangojs/router/server"
4955
+ );
4956
+ registry = serverMod.RouterRegistry ?? null;
4957
+ } catch (err) {
4958
+ console.warn(
4959
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
4960
+ );
4961
+ res.statusCode = 500;
4962
+ res.end(`Prerender handler error: ${err.message}`);
4963
+ return;
4964
+ }
4965
+ } else {
4966
+ registry = mainRegistry;
4967
+ }
4789
4968
  if (!registry) {
4790
4969
  if (!prerenderNodeRegistry) {
4791
4970
  await getOrCreateTempServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.88a3b2f7",
3
+ "version": "0.0.0-experimental.8bd1b239",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -142,7 +142,7 @@
142
142
  "test:unit:watch": "vitest"
143
143
  },
144
144
  "dependencies": {
145
- "@vitejs/plugin-rsc": "^0.5.19",
145
+ "@vitejs/plugin-rsc": "^0.5.23",
146
146
  "magic-string": "^0.30.17",
147
147
  "picomatch": "^4.0.3",
148
148
  "rsc-html-stream": "^0.0.7"
@@ -162,7 +162,7 @@
162
162
  },
163
163
  "peerDependencies": {
164
164
  "@cloudflare/vite-plugin": "^1.25.0",
165
- "@vitejs/plugin-rsc": "^0.5.14",
165
+ "@vitejs/plugin-rsc": "^0.5.23",
166
166
  "react": "^18.0.0 || ^19.0.0",
167
167
  "vite": "^7.3.0"
168
168
  },