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

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 (102) hide show
  1. package/README.md +50 -20
  2. package/dist/vite/index.js +647 -176
  3. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  4. package/package.json +7 -5
  5. package/skills/breadcrumbs/SKILL.md +3 -1
  6. package/skills/handler-use/SKILL.md +362 -0
  7. package/skills/hooks/SKILL.md +28 -20
  8. package/skills/intercept/SKILL.md +20 -0
  9. package/skills/layout/SKILL.md +22 -0
  10. package/skills/links/SKILL.md +88 -16
  11. package/skills/loader/SKILL.md +35 -2
  12. package/skills/middleware/SKILL.md +32 -3
  13. package/skills/migrate-nextjs/SKILL.md +560 -0
  14. package/skills/migrate-react-router/SKILL.md +765 -0
  15. package/skills/parallel/SKILL.md +59 -0
  16. package/skills/rango/SKILL.md +24 -22
  17. package/skills/response-routes/SKILL.md +8 -0
  18. package/skills/route/SKILL.md +24 -0
  19. package/skills/streams-and-websockets/SKILL.md +283 -0
  20. package/skills/typesafety/SKILL.md +3 -1
  21. package/src/browser/app-shell.ts +52 -0
  22. package/src/browser/navigation-bridge.ts +72 -4
  23. package/src/browser/navigation-client.ts +64 -13
  24. package/src/browser/navigation-store.ts +25 -1
  25. package/src/browser/partial-update.ts +34 -3
  26. package/src/browser/prefetch/cache.ts +129 -21
  27. package/src/browser/prefetch/fetch.ts +148 -16
  28. package/src/browser/prefetch/queue.ts +36 -5
  29. package/src/browser/rango-state.ts +53 -13
  30. package/src/browser/react/Link.tsx +30 -2
  31. package/src/browser/react/NavigationProvider.tsx +50 -11
  32. package/src/browser/react/use-navigation.ts +22 -2
  33. package/src/browser/react/use-params.ts +11 -1
  34. package/src/browser/react/use-router.ts +8 -1
  35. package/src/browser/rsc-router.tsx +34 -6
  36. package/src/browser/segment-reconciler.ts +36 -14
  37. package/src/browser/types.ts +13 -0
  38. package/src/build/route-trie.ts +50 -24
  39. package/src/cache/cf/cf-cache-store.ts +5 -7
  40. package/src/client.tsx +84 -230
  41. package/src/index.rsc.ts +3 -0
  42. package/src/index.ts +44 -9
  43. package/src/outlet-context.ts +1 -1
  44. package/src/response-utils.ts +28 -0
  45. package/src/reverse.ts +7 -3
  46. package/src/route-definition/dsl-helpers.ts +180 -24
  47. package/src/route-definition/helpers-types.ts +61 -14
  48. package/src/route-definition/resolve-handler-use.ts +6 -0
  49. package/src/route-types.ts +7 -0
  50. package/src/router/handler-context.ts +24 -4
  51. package/src/router/lazy-includes.ts +6 -6
  52. package/src/router/loader-resolution.ts +73 -46
  53. package/src/router/manifest.ts +22 -13
  54. package/src/router/match-api.ts +3 -3
  55. package/src/router/match-middleware/cache-lookup.ts +10 -5
  56. package/src/router/match-middleware/segment-resolution.ts +1 -1
  57. package/src/router/match-result.ts +82 -4
  58. package/src/router/middleware-types.ts +2 -22
  59. package/src/router/middleware.ts +32 -4
  60. package/src/router/pattern-matching.ts +60 -9
  61. package/src/router/segment-resolution/fresh.ts +52 -0
  62. package/src/router/segment-resolution/revalidation.ts +69 -1
  63. package/src/router/trie-matching.ts +10 -4
  64. package/src/router/url-params.ts +49 -0
  65. package/src/router.ts +1 -2
  66. package/src/rsc/handler.ts +21 -9
  67. package/src/rsc/helpers.ts +69 -41
  68. package/src/rsc/loader-fetch.ts +23 -3
  69. package/src/rsc/progressive-enhancement.ts +12 -2
  70. package/src/rsc/response-route-handler.ts +14 -1
  71. package/src/rsc/rsc-rendering.ts +12 -1
  72. package/src/rsc/server-action.ts +8 -0
  73. package/src/rsc/types.ts +1 -0
  74. package/src/segment-content-promise.ts +67 -0
  75. package/src/segment-loader-promise.ts +122 -0
  76. package/src/segment-system.tsx +11 -61
  77. package/src/server/context.ts +26 -3
  78. package/src/server/handle-store.ts +19 -0
  79. package/src/server/request-context.ts +64 -56
  80. package/src/types/handler-context.ts +2 -34
  81. package/src/types/loader-types.ts +5 -6
  82. package/src/types/request-scope.ts +126 -0
  83. package/src/types/route-entry.ts +11 -0
  84. package/src/types/segments.ts +1 -1
  85. package/src/urls/include-helper.ts +24 -14
  86. package/src/urls/path-helper-types.ts +34 -5
  87. package/src/urls/response-types.ts +2 -10
  88. package/src/use-loader.tsx +77 -5
  89. package/src/vite/debug.ts +55 -0
  90. package/src/vite/discovery/prerender-collection.ts +124 -83
  91. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  92. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  93. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  94. package/src/vite/plugins/expose-id-utils.ts +12 -0
  95. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  96. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  97. package/src/vite/plugins/performance-tracks.ts +4 -6
  98. package/src/vite/rango.ts +49 -14
  99. package/src/vite/router-discovery.ts +186 -26
  100. package/src/vite/utils/banner.ts +1 -1
  101. package/src/vite/utils/package-resolution.ts +41 -1
  102. package/src/vite/utils/prerender-utils.ts +20 -6
@@ -2,7 +2,12 @@ import type { Plugin, ResolvedConfig } from "vite";
2
2
  import { parseAst } from "vite";
3
3
  import MagicString from "magic-string";
4
4
  import path from "node:path";
5
- import { normalizePath, hashId, detectImports } from "./expose-id-utils.js";
5
+ import {
6
+ normalizePath,
7
+ hashId,
8
+ makeStubId,
9
+ detectImports,
10
+ } from "./expose-id-utils.js";
6
11
  import {
7
12
  transformInlineHandlers,
8
13
  type VirtualHandlerEntry,
@@ -23,6 +28,7 @@ import {
23
28
  getImportedFnNames,
24
29
  collectCreateExportBindings,
25
30
  buildUnsupportedShapeWarning,
31
+ isExportOnlyFile,
26
32
  } from "./expose-ids/export-analysis.js";
27
33
  import {
28
34
  hasCreateLoaderImport,
@@ -34,6 +40,7 @@ import {
34
40
  transformLocationState,
35
41
  generateWholeFileStubs,
36
42
  generateExprStubs,
43
+ stubHandlerExprs,
37
44
  transformHandlerIds,
38
45
  } from "./expose-ids/handler-transform.js";
39
46
 
@@ -385,7 +392,9 @@ ${lazyImports.join(",\n")}
385
392
  if (stubResult) return stubResult;
386
393
  }
387
394
 
388
- // --- PrerenderHandler: non-RSC stub replacement ---
395
+ // --- PrerenderHandler: non-RSC whole-file stub replacement ---
396
+ // When ALL exports are Prerender() calls, replace the entire file.
397
+ // Mixed-export files are handled in the unified pipeline below.
389
398
  if (hasPrerenderHandlerCode && !isRscEnv) {
390
399
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
391
400
  const bindings = getBindings(code, fnNames);
@@ -397,16 +406,6 @@ ${lazyImports.join(",\n")}
397
406
  isBuild,
398
407
  );
399
408
  if (wholeFile) return wholeFile;
400
-
401
- const exprStubs = generateExprStubs(
402
- PRERENDER_CONFIG,
403
- bindings,
404
- code,
405
- filePath,
406
- id,
407
- isBuild,
408
- );
409
- if (exprStubs) return exprStubs;
410
409
  }
411
410
 
412
411
  // --- PrerenderHandler: RSC build module tracking ---
@@ -467,7 +466,8 @@ ${lazyImports.join(",\n")}
467
466
  }
468
467
  }
469
468
 
470
- // --- StaticHandler: non-RSC stub replacement ---
469
+ // --- StaticHandler: non-RSC whole-file stub replacement ---
470
+ // When ALL exports are Static() calls, replace the entire file.
471
471
  if (hasStaticHandlerCode && !isRscEnv) {
472
472
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
473
473
  const bindings = getBindings(code, fnNames);
@@ -479,16 +479,212 @@ ${lazyImports.join(",\n")}
479
479
  isBuild,
480
480
  );
481
481
  if (wholeFile) return wholeFile;
482
+ }
482
483
 
483
- const exprStubs = generateExprStubs(
484
- STATIC_CONFIG,
485
- bindings,
486
- code,
487
- filePath,
488
- id,
489
- isBuild,
490
- );
491
- if (exprStubs) return exprStubs;
484
+ // --- Mixed-type whole-file stub replacement (non-RSC) ---
485
+ // When the individual whole-file checks above fail (each only checks
486
+ // one type), the file has mixed exports (e.g. createLoader + Prerender).
487
+ // Gather ALL stub-safe bindings and check if they cover every export.
488
+ // If yes, replace the entire file with stubs — this strips server-only
489
+ // imports (node:fs, DB clients, etc.) that would crash in the browser.
490
+ //
491
+ // Only applies when the file contains Prerender/Static (the handler
492
+ // types that bring server-only code). Files with only loaders, handles,
493
+ // or locationState are handled correctly by the unified pipeline below.
494
+ //
495
+ // Loader, Prerender, and Static exports become plain { __brand, $$id }
496
+ // stubs. createHandle and createLocationState need their create*()
497
+ // functions to execute (collect registration / __rsc_ls_key), so their
498
+ // call expressions are preserved with only a @rangojs/router import.
499
+ // This strips all server-only imports while keeping the correct
500
+ // client contract for every export type.
501
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
502
+ const prerenderFnNames = hasPrerenderHandlerCode
503
+ ? getFnNames(PRERENDER_CONFIG.fnName)
504
+ : [];
505
+ const staticFnNames = hasStaticHandlerCode
506
+ ? getFnNames(STATIC_CONFIG.fnName)
507
+ : [];
508
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
509
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
510
+ const lsFnNames = hasLocationStateCode
511
+ ? getFnNames("createLocationState")
512
+ : [];
513
+
514
+ // Collect ALL recognized bindings to check export coverage
515
+ const allBindings: CreateExportBinding[] = [];
516
+ for (const fnNames of [
517
+ prerenderFnNames,
518
+ staticFnNames,
519
+ loaderFnNames,
520
+ handleFnNames,
521
+ lsFnNames,
522
+ ]) {
523
+ if (fnNames.length > 0) {
524
+ allBindings.push(...getBindings(code, fnNames));
525
+ }
526
+ }
527
+
528
+ // Check if preserved createHandle/createLocationState calls
529
+ // reference non-exported locals (e.g. helper functions, constants).
530
+ // If so, the whole-file stub would strip those locals, breaking
531
+ // the call. Fall through to the unified pipeline instead.
532
+ let canStubWholeFile =
533
+ allBindings.length > 0 && isExportOnlyFile(code, allBindings);
534
+
535
+ if (
536
+ canStubWholeFile &&
537
+ (handleFnNames.length > 0 || lsFnNames.length > 0)
538
+ ) {
539
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
540
+ // Collect bindings that would be stripped by whole-file replacement:
541
+ // local declarations and imported bindings from non-@rangojs/router
542
+ // modules. This is a regex-based heuristic — it intentionally skips
543
+ // edge cases (class decls, destructured bindings, combined
544
+ // default+named imports) since those rarely appear in route files.
545
+ const strippedBindings: string[] = [];
546
+
547
+ // Skip React Fast Refresh temporaries (_c, _c2, ...) which are
548
+ // injected by @vitejs/plugin-react in the client environment and
549
+ // would falsely trigger the bailout.
550
+ const localDeclPattern =
551
+ /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
552
+ let declMatch: RegExpExecArray | null;
553
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
554
+ const name = declMatch[1];
555
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
556
+ strippedBindings.push(name);
557
+ }
558
+ }
559
+
560
+ const importPattern =
561
+ /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
562
+ let importMatch: RegExpExecArray | null;
563
+ while ((importMatch = importPattern.exec(code)) !== null) {
564
+ for (const spec of importMatch[1].split(",")) {
565
+ const m = spec
566
+ .trim()
567
+ .match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
568
+ if (m) strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
569
+ }
570
+ }
571
+ const defaultImportPattern =
572
+ /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
573
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
574
+ strippedBindings.push(importMatch[1]);
575
+ }
576
+ const nsImportPattern =
577
+ /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
578
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
579
+ strippedBindings.push(importMatch[1]);
580
+ }
581
+
582
+ if (strippedBindings.length > 0) {
583
+ const preservedBindings = allBindings.filter((b) => {
584
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
585
+ return (
586
+ handleFnNames.some((n) => fc.includes(n)) ||
587
+ lsFnNames.some((n) => fc.includes(n))
588
+ );
589
+ });
590
+ const strippedRe = new RegExp(
591
+ `\\b(?:${strippedBindings.join("|")})\\b`,
592
+ );
593
+ canStubWholeFile = !preservedBindings.some((b) => {
594
+ const expr = code.slice(b.callExprStart, b.callCloseParenPos + 1);
595
+ return strippedRe.test(expr);
596
+ });
597
+ }
598
+ }
599
+
600
+ if (canStubWholeFile) {
601
+ const lines: string[] = [];
602
+ const neededImports: string[] = [];
603
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
604
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
605
+ if (neededImports.length > 0) {
606
+ lines.push(
607
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`,
608
+ );
609
+ }
610
+
611
+ for (const binding of allBindings) {
612
+ const fnCall = code.slice(
613
+ binding.callExprStart,
614
+ binding.callOpenParenPos + 1,
615
+ );
616
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
617
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
618
+
619
+ // Aliases share the primary name's ID (matches server transforms).
620
+ const primaryName = binding.exportNames[0];
621
+ const stubId = makeStubId(filePath, primaryName, isBuild);
622
+
623
+ if (isHandle || isLocationState) {
624
+ // Rewrite alias to canonical name since the stub file only
625
+ // imports canonical names from @rangojs/router.
626
+ // Strip React Fast Refresh `_c = ` wrappers from args
627
+ // (e.g. `_c = (segments) => ...` → `(segments) => ...`)
628
+ const rawArgs = code
629
+ .slice(binding.callOpenParenPos + 1, binding.callCloseParenPos)
630
+ .replace(/\b_c\d*\s*=\s*/g, "");
631
+ const canonicalName = isHandle
632
+ ? "createHandle"
633
+ : "createLocationState";
634
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
635
+
636
+ // Reconstruct the function name (handling aliases + generics)
637
+ let rawCallee = code.slice(
638
+ binding.callExprStart,
639
+ binding.callOpenParenPos,
640
+ );
641
+ for (const alias of activeFnNames) {
642
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
643
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
644
+ break;
645
+ }
646
+ }
647
+
648
+ if (isHandle) {
649
+ // createHandle checks __injectedId DURING the call, so $$id
650
+ // must be a parameter, not a post-call property assignment.
651
+ const idParam =
652
+ binding.argCount === 0
653
+ ? `undefined, "${stubId}"`
654
+ : `, "${stubId}"`;
655
+ lines.push(
656
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`,
657
+ );
658
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
659
+ } else {
660
+ lines.push(
661
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`,
662
+ );
663
+ lines.push(
664
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`,
665
+ );
666
+ }
667
+ for (const name of binding.exportNames.slice(1)) {
668
+ lines.push(`export const ${name} = ${primaryName};`);
669
+ }
670
+ } else {
671
+ let brand = "loader";
672
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
673
+ brand = PRERENDER_CONFIG.brand;
674
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
675
+ brand = STATIC_CONFIG.brand;
676
+ }
677
+ lines.push(
678
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`,
679
+ );
680
+ for (const name of binding.exportNames.slice(1)) {
681
+ lines.push(`export const ${name} = ${primaryName};`);
682
+ }
683
+ }
684
+ }
685
+
686
+ return { code: lines.join("\n") + "\n", map: null };
687
+ }
492
688
  }
493
689
 
494
690
  // --- StaticHandler: RSC build module tracking ---
@@ -535,27 +731,48 @@ ${lazyImports.join(",\n")}
535
731
  isBuild,
536
732
  ) || changed;
537
733
  }
538
- if (hasPrerenderHandlerCode && isRscEnv) {
734
+ if (hasPrerenderHandlerCode) {
539
735
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
540
- changed =
541
- transformHandlerIds(
542
- PRERENDER_CONFIG,
543
- getBindings(code, fnNames),
544
- s,
545
- filePath,
546
- isBuild,
547
- ) || changed;
736
+ const bindings = getBindings(code, fnNames);
737
+ if (isRscEnv) {
738
+ changed =
739
+ transformHandlerIds(
740
+ PRERENDER_CONFIG,
741
+ bindings,
742
+ s,
743
+ filePath,
744
+ isBuild,
745
+ ) || changed;
746
+ } else {
747
+ // Non-RSC mixed-export file: replace Prerender() calls with stubs
748
+ // on the shared MagicString so sourcemaps stay accurate.
749
+ changed =
750
+ stubHandlerExprs(
751
+ PRERENDER_CONFIG,
752
+ bindings,
753
+ s,
754
+ filePath,
755
+ isBuild,
756
+ ) || changed;
757
+ }
548
758
  }
549
- if (hasStaticHandlerCode && isRscEnv) {
759
+ if (hasStaticHandlerCode) {
550
760
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
551
- changed =
552
- transformHandlerIds(
553
- STATIC_CONFIG,
554
- getBindings(code, fnNames),
555
- s,
556
- filePath,
557
- isBuild,
558
- ) || changed;
761
+ const bindings = getBindings(code, fnNames);
762
+ if (isRscEnv) {
763
+ changed =
764
+ transformHandlerIds(
765
+ STATIC_CONFIG,
766
+ bindings,
767
+ s,
768
+ filePath,
769
+ isBuild,
770
+ ) || changed;
771
+ } else {
772
+ changed =
773
+ stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
774
+ changed;
775
+ }
559
776
  }
560
777
 
561
778
  if (!changed) return;
@@ -14,6 +14,9 @@
14
14
 
15
15
  import type { Plugin } from "vite";
16
16
  import { readFile } from "node:fs/promises";
17
+ import { createRangoDebugger } from "../debug.js";
18
+
19
+ const debug = createRangoDebugger("rango:transform");
17
20
 
18
21
  const RSDW_PATCH_RE =
19
22
  /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
@@ -76,12 +79,7 @@ export function performanceTracksPlugin(): Plugin {
76
79
  if (!id.includes("react-server-dom") || !id.includes("client")) return;
77
80
  const patched = patchRsdwClientDebugInfoRecovery(code);
78
81
  if (!patched.debugInfoVar) return;
79
- if (process.env.INTERNAL_RANGO_DEBUG)
80
- console.log(
81
- "[perf-tracks] patched RSDW client (var:",
82
- patched.debugInfoVar,
83
- ")",
84
- );
82
+ debug?.("patched RSDW client (var: %s)", patched.debugInfoVar);
85
83
  return patched.code;
86
84
  },
87
85
  };
package/src/vite/rango.ts CHANGED
@@ -12,6 +12,8 @@ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
12
  import {
13
13
  getExcludeDeps,
14
14
  getPackageAliases,
15
+ getPublishedPackageName,
16
+ getVendorAliases,
15
17
  } from "./utils/package-resolution.js";
16
18
  import { findRouterFiles } from "../build/generate-route-types.js";
17
19
  import { createVersionPlugin } from "./plugins/version-plugin.js";
@@ -27,6 +29,9 @@ import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
27
29
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
28
30
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
31
  import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
32
+ import { createRangoDebugger } from "./debug.js";
33
+
34
+ const debugConfig = createRangoDebugger("rango:config");
30
35
 
31
36
  /**
32
37
  * Vite plugin for @rangojs/router.
@@ -53,25 +58,41 @@ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
53
58
  * ```
54
59
  */
55
60
  export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
61
+ const rangoStart = performance.now();
56
62
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
57
63
  const preset = resolvedOptions.preset ?? "node";
58
64
  const showBanner = resolvedOptions.banner ?? true;
65
+ debugConfig?.("rango(%s) setup start", preset);
59
66
 
60
67
  const plugins: PluginOption[] = [];
61
68
 
62
- // Get package resolution info (workspace vs npm install)
63
- const rangoAliases = getPackageAliases();
69
+ // Get package resolution info (workspace vs npm install).
70
+ // Vendor aliases redirect the bare plugin-rsc vendor specs (which plugin-rsc
71
+ // itself injects into optimizeDeps.include) to absolute paths resolved from
72
+ // this package — so strict-pnpm consumers don't hit "Failed to resolve
73
+ // dependency" warnings when those deps aren't hoisted to their app root.
74
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
64
75
  const excludeDeps = [
65
76
  ...getExcludeDeps(),
66
- // The public browser entry re-exports the RSDW browser client.
67
- // Excluding both keeps Vite from freezing the unpatched bundle into
68
- // .vite/deps before our source transforms run.
77
+ // plugin-rsc itself injects these into the client env's
78
+ // optimizeDeps.include, which overrides exclude for the dep's own
79
+ // pre-bundle entry. What exclude still controls is how *other*
80
+ // pre-bundled deps treat imports of these specs (external vs inlined)
81
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
82
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
83
+ // where client.browser's bare include fails to resolve and Vite ends up
84
+ // serving the raw CJS file at dev-serve time.
69
85
  "@vitejs/plugin-rsc/browser",
70
- // Keep the browser RSDW client out of Vite's dep optimizer so our
71
- // cjs-to-esm transform can patch the real file.
72
86
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
73
87
  ];
74
88
 
89
+ // Vite supports a nested `A > B` syntax in optimizeDeps.include that resolves
90
+ // B from A's location. We anchor transitive deps (rsc-html-stream,
91
+ // @vitejs/plugin-rsc/vendor/*) to @rangojs/router so pnpm consumers — where
92
+ // these aren't visible at the app root — can still pre-bundle them.
93
+ const pkg = getPublishedPackageName();
94
+ const nested = (spec: string) => `${pkg} > ${spec}`;
95
+
75
96
  // Mutable ref for router path (node preset only).
76
97
  // Set immediately when user-specified, or populated by the auto-discover
77
98
  // config() hook using Vite's resolved root.
@@ -126,7 +147,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
126
147
  // Pre-bundle rsc-html-stream to prevent discovery during first request
127
148
  // Exclude rsc-router modules to ensure same Context instance
128
149
  optimizeDeps: {
129
- include: ["rsc-html-stream/client"],
150
+ include: [nested("rsc-html-stream/client")],
130
151
  exclude: excludeDeps,
131
152
  esbuildOptions: sharedEsbuildOptions,
132
153
  },
@@ -151,8 +172,10 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
151
172
  "react-dom/static.edge",
152
173
  "react/jsx-runtime",
153
174
  "react/jsx-dev-runtime",
154
- "rsc-html-stream/server",
155
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
175
+ nested("rsc-html-stream/server"),
176
+ nested(
177
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
178
+ ),
156
179
  ],
157
180
  exclude: excludeDeps,
158
181
  esbuildOptions: sharedEsbuildOptions,
@@ -167,7 +190,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
167
190
  "react",
168
191
  "react/jsx-runtime",
169
192
  "react/jsx-dev-runtime",
170
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
193
+ nested(
194
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
195
+ ),
171
196
  ],
172
197
  exclude: excludeDeps,
173
198
  esbuildOptions: sharedEsbuildOptions,
@@ -280,7 +305,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
280
305
  "react-dom",
281
306
  "react/jsx-runtime",
282
307
  "react/jsx-dev-runtime",
283
- "rsc-html-stream/client",
308
+ nested("rsc-html-stream/client"),
284
309
  ],
285
310
  exclude: excludeDeps,
286
311
  esbuildOptions: sharedEsbuildOptions,
@@ -297,7 +322,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
297
322
  "react-dom/static.edge",
298
323
  "react/jsx-runtime",
299
324
  "react/jsx-dev-runtime",
300
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
325
+ nested(
326
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
327
+ ),
301
328
  ],
302
329
  exclude: excludeDeps,
303
330
  esbuildOptions: sharedEsbuildOptions,
@@ -310,7 +337,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
310
337
  "react",
311
338
  "react/jsx-runtime",
312
339
  "react/jsx-dev-runtime",
313
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
340
+ nested(
341
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
342
+ ),
314
343
  ],
315
344
  esbuildOptions: sharedEsbuildOptions,
316
345
  },
@@ -458,5 +487,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
458
487
  }),
459
488
  );
460
489
 
490
+ debugConfig?.(
491
+ "rango(%s) setup done: %d plugin(s) (%sms)",
492
+ preset,
493
+ plugins.length,
494
+ (performance.now() - rangoStart).toFixed(1),
495
+ );
461
496
  return plugins;
462
497
  }