@rangojs/router 0.0.0-experimental.002d056c

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 (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,517 @@
1
+ import type MagicString from "magic-string";
2
+ import { hashInlineId, buildExportMap } from "../plugins/expose-id-utils.js";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Types
6
+ // ---------------------------------------------------------------------------
7
+
8
+ /** Minimal ESTree Program node — avoids importing from `rollup` (not a direct dep). */
9
+ interface ProgramNode {
10
+ type: "Program";
11
+ body: any[];
12
+ }
13
+
14
+ export interface HandlerCallSite {
15
+ callStart: number;
16
+ callEnd: number;
17
+ argCount: number;
18
+ lineNumber: number;
19
+ calleeName: string;
20
+
21
+ exportInfo: {
22
+ exportName: string;
23
+ statementEnd: number;
24
+ } | null;
25
+ }
26
+
27
+ export interface VirtualHandlerEntry {
28
+ originalModuleId: string;
29
+ imports: string[];
30
+ declarations: string[];
31
+ handlerCode: string;
32
+ exportName: string;
33
+ }
34
+
35
+ function isDirectivePrologueStatement(node: any): boolean {
36
+ return (
37
+ node?.type === "ExpressionStatement" && typeof node.directive === "string"
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Find where generated imports should be inserted:
43
+ * after the directive prologue and any contiguous import declarations.
44
+ */
45
+ function findImportInsertionPos(
46
+ code: string,
47
+ parseAst: (code: string, options?: any) => ProgramNode,
48
+ ): number {
49
+ let program: ProgramNode;
50
+ try {
51
+ program = parseAst(code, { jsx: true });
52
+ } catch {
53
+ return 0;
54
+ }
55
+
56
+ const body = program.body as any[];
57
+ let i = 0;
58
+ let insertionPos = 0;
59
+
60
+ while (i < body.length && isDirectivePrologueStatement(body[i])) {
61
+ insertionPos = body[i].end;
62
+ i++;
63
+ }
64
+
65
+ while (i < body.length && body[i]?.type === "ImportDeclaration") {
66
+ insertionPos = body[i].end;
67
+ i++;
68
+ }
69
+
70
+ return insertionPos;
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // AST walking helper
75
+ // ---------------------------------------------------------------------------
76
+
77
+ /**
78
+ * Recursively walk an ESTree AST node, calling `enter` on each node.
79
+ * Parent is passed for context.
80
+ */
81
+ function walkNode(
82
+ node: any,
83
+ parent: any,
84
+ ancestors: any[],
85
+ enter: (node: any, parent: any, ancestors: any[]) => void,
86
+ ): void {
87
+ if (!node || typeof node !== "object") return;
88
+ if (typeof node.type !== "string") return;
89
+
90
+ ancestors.push(node);
91
+ enter(node, parent, ancestors);
92
+
93
+ for (const key of Object.keys(node)) {
94
+ if (key === "type" || key === "start" || key === "end") continue;
95
+ const child = node[key];
96
+ if (Array.isArray(child)) {
97
+ for (const item of child) {
98
+ if (item && typeof item === "object" && typeof item.type === "string") {
99
+ walkNode(item, node, ancestors, enter);
100
+ }
101
+ }
102
+ } else if (
103
+ child &&
104
+ typeof child === "object" &&
105
+ typeof child.type === "string"
106
+ ) {
107
+ walkNode(child, node, ancestors, enter);
108
+ }
109
+ }
110
+
111
+ ancestors.pop();
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // AST analysis
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * Parse the file with Vite's parseAst and find all calls to `fnName`.
120
+ * Distinguishes between `export const X = fnName(...)` (exportInfo set)
121
+ * and inline calls like `layout(fnName(...))` (exportInfo null).
122
+ */
123
+ export function findHandlerCalls(
124
+ code: string,
125
+ fnName: string,
126
+ parseAst: (code: string, options?: any) => ProgramNode,
127
+ ): HandlerCallSite[] {
128
+ let program: ProgramNode;
129
+ try {
130
+ program = parseAst(code, { jsx: true });
131
+ } catch {
132
+ return [];
133
+ }
134
+
135
+ const sites: HandlerCallSite[] = [];
136
+ const localNames = getImportedLocalNamesFromProgram(program, fnName);
137
+ const exportedNamesByLocal = buildExportMap(program);
138
+
139
+ walkNode(program, null, [], (node: any, parent: any, ancestors: any[]) => {
140
+ if (
141
+ node.type !== "CallExpression" ||
142
+ node.callee?.type !== "Identifier" ||
143
+ !localNames.has(node.callee.name)
144
+ ) {
145
+ return;
146
+ }
147
+
148
+ const callStart: number = node.start;
149
+ const callEnd: number = node.end;
150
+ const argCount: number = node.arguments?.length ?? 0;
151
+ const calleeName: string = node.callee.name;
152
+
153
+ // Compute 1-based line number
154
+ let lineNumber = 1;
155
+ for (let i = 0; i < callStart && i < code.length; i++) {
156
+ if (code[i] === "\n") lineNumber++;
157
+ }
158
+
159
+ // Check if this is an export const pattern:
160
+ // ExportNamedDeclaration > VariableDeclaration > VariableDeclarator(init=CallExpression)
161
+ let exportInfo: HandlerCallSite["exportInfo"] = null;
162
+
163
+ if (parent?.type === "VariableDeclarator" && parent.init === node) {
164
+ // ancestors: [..., ExportNamedDecl, VarDecl, VarDeclarator, CallExpr]
165
+ const grandParent =
166
+ ancestors.length >= 3 ? ancestors[ancestors.length - 3] : null;
167
+ const greatGrandParent =
168
+ ancestors.length >= 4 ? ancestors[ancestors.length - 4] : null;
169
+
170
+ if (
171
+ grandParent?.type === "VariableDeclaration" &&
172
+ greatGrandParent?.type === "ExportNamedDeclaration"
173
+ ) {
174
+ const exportName = parent.id?.name;
175
+ if (exportName) {
176
+ exportInfo = {
177
+ exportName,
178
+ statementEnd: greatGrandParent.end,
179
+ };
180
+ }
181
+ } else if (
182
+ grandParent?.type === "VariableDeclaration" &&
183
+ parent.id?.type === "Identifier"
184
+ ) {
185
+ const exportedNames = exportedNamesByLocal.get(parent.id.name);
186
+ if (exportedNames && exportedNames.length > 0) {
187
+ exportInfo = {
188
+ exportName: exportedNames[0],
189
+ statementEnd: grandParent.end,
190
+ };
191
+ }
192
+ }
193
+ }
194
+
195
+ sites.push({
196
+ callStart,
197
+ callEnd,
198
+ argCount,
199
+ lineNumber,
200
+ calleeName,
201
+ exportInfo,
202
+ });
203
+ });
204
+
205
+ return sites;
206
+ }
207
+
208
+ function getImportedLocalNamesFromProgram(
209
+ program: ProgramNode,
210
+ importedName: string,
211
+ ): Set<string> {
212
+ const localNames = new Set<string>();
213
+ const body = program.body as any[];
214
+
215
+ for (const node of body) {
216
+ if (node?.type !== "ImportDeclaration") continue;
217
+ const source = node.source?.value;
218
+ if (typeof source !== "string") continue;
219
+ if (!source.startsWith("@rangojs/router")) continue;
220
+
221
+ const specifiers = Array.isArray(node.specifiers) ? node.specifiers : [];
222
+ for (const spec of specifiers) {
223
+ if (spec?.type !== "ImportSpecifier") continue;
224
+ if (spec.imported?.type !== "Identifier") continue;
225
+ if (spec.imported.name !== importedName) continue;
226
+
227
+ if (spec.local?.type === "Identifier") {
228
+ localNames.add(spec.local.name);
229
+ }
230
+ }
231
+ }
232
+
233
+ return localNames;
234
+ }
235
+
236
+ export function getImportedLocalNames(
237
+ code: string,
238
+ importedName: string,
239
+ parseAst: (code: string, options?: any) => ProgramNode,
240
+ ): Set<string> {
241
+ try {
242
+ const program = parseAst(code, { jsx: true });
243
+ return getImportedLocalNamesFromProgram(program, importedName);
244
+ } catch {
245
+ return new Set<string>();
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Extract all import declarations from the source as raw text slices.
251
+ * Copies ALL imports -- Rollup tree-shakes unused ones from virtual modules.
252
+ */
253
+ export function extractImportDeclarations(
254
+ code: string,
255
+ parseAst: (code: string, options?: any) => ProgramNode,
256
+ ): string[] {
257
+ let program: ProgramNode;
258
+ try {
259
+ program = parseAst(code, { jsx: true });
260
+ } catch {
261
+ return [];
262
+ }
263
+
264
+ const imports: string[] = [];
265
+ for (const node of program.body as any[]) {
266
+ if (node.type === "ImportDeclaration") {
267
+ imports.push(code.slice(node.start, node.end));
268
+ }
269
+ }
270
+ return imports;
271
+ }
272
+
273
+ /**
274
+ * Check if an expression AST subtree is "inert" -- safe to evaluate eagerly
275
+ * without referencing import bindings. Inert expressions contain only literals,
276
+ * arrays/objects of inert values, template literals with inert expressions,
277
+ * and unary/binary operators on inert operands.
278
+ *
279
+ * Function/arrow expressions are NOT inert (they're lazy -- handled separately).
280
+ * Identifiers and member expressions are NOT inert (may reference imports).
281
+ *
282
+ * This check prevents TDZ errors when declarations are moved to virtual
283
+ * modules that end up in separate Rollup chunks with circular dependencies.
284
+ */
285
+ function isInertExpression(node: any): boolean {
286
+ if (!node) return false;
287
+ switch (node.type) {
288
+ case "Literal":
289
+ return true;
290
+ case "TemplateLiteral":
291
+ return (node.expressions ?? []).every((e: any) => isInertExpression(e));
292
+ case "ArrayExpression":
293
+ return (node.elements ?? []).every(
294
+ (e: any) => e === null || isInertExpression(e),
295
+ );
296
+ case "ObjectExpression":
297
+ return (node.properties ?? []).every(
298
+ (p: any) =>
299
+ p.type === "Property" &&
300
+ (!p.computed || isInertExpression(p.key)) &&
301
+ isInertExpression(p.value),
302
+ );
303
+ case "UnaryExpression":
304
+ return isInertExpression(node.argument);
305
+ case "BinaryExpression":
306
+ return isInertExpression(node.left) && isInertExpression(node.right);
307
+ case "ConditionalExpression":
308
+ return (
309
+ isInertExpression(node.test) &&
310
+ isInertExpression(node.consequent) &&
311
+ isInertExpression(node.alternate)
312
+ );
313
+ case "SpreadElement":
314
+ return isInertExpression(node.argument);
315
+ default:
316
+ return false;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Check if a variable declarator's init is safe for inclusion in a virtual
322
+ * module. Safe initializers are:
323
+ * 1. Function/arrow expressions (body is lazy, no TDZ risk)
324
+ * 2. Inert expressions (no identifier references, no TDZ risk)
325
+ *
326
+ * Declarations that reference identifiers at the top level (e.g.
327
+ * `const VT = React.Fragment`) are NOT safe -- when the virtual module
328
+ * is bundled into a separate Rollup chunk, circular chunk dependencies
329
+ * can cause "Cannot access X before initialization" TDZ errors.
330
+ */
331
+ function isSafeDeclaratorInit(init: any): boolean {
332
+ if (!init) return true; // `let x;` with no init is safe
333
+ if (
334
+ init.type === "ArrowFunctionExpression" ||
335
+ init.type === "FunctionExpression"
336
+ ) {
337
+ return true;
338
+ }
339
+ return isInertExpression(init);
340
+ }
341
+
342
+ /**
343
+ * Check if all declarators in a VariableDeclaration have safe initializers
344
+ * and none is a handler call (Static/Prerender).
345
+ */
346
+ function isSafeVariableDeclaration(
347
+ node: any,
348
+ handlerNames: Set<string>,
349
+ ): boolean {
350
+ if (node.type !== "VariableDeclaration") return false;
351
+ return node.declarations.every(
352
+ (d: any) =>
353
+ isSafeDeclaratorInit(d.init) &&
354
+ !(
355
+ d.init?.type === "CallExpression" &&
356
+ d.init.callee?.type === "Identifier" &&
357
+ handlerNames.has(d.init.callee.name)
358
+ ),
359
+ );
360
+ }
361
+
362
+ /**
363
+ * Extract module-level declarations that are safe for inclusion in virtual
364
+ * modules. "Safe" means the declaration can be eagerly evaluated without
365
+ * referencing import bindings, preventing TDZ errors in separate chunks.
366
+ *
367
+ * Included: function declarations, arrow/function expression inits, and
368
+ * variable inits that are inert (pure literals, arrays, objects).
369
+ * Excluded: declarations that reference identifiers at init time (may
370
+ * reference imports causing TDZ), handler call declarations (circular),
371
+ * class declarations (field initializers can reference imports).
372
+ *
373
+ * Strips export keywords so declarations work as plain locals.
374
+ * Rollup tree-shakes unused declarations from virtual modules.
375
+ */
376
+ export function extractModuleLevelDeclarations(
377
+ code: string,
378
+ parseAst: (code: string, options?: any) => ProgramNode,
379
+ handlerNames: Set<string>,
380
+ ): string[] {
381
+ let program: ProgramNode;
382
+ try {
383
+ program = parseAst(code, { jsx: true });
384
+ } catch {
385
+ return [];
386
+ }
387
+
388
+ const declarations: string[] = [];
389
+ for (const node of program.body as any[]) {
390
+ // Skip imports (handled by extractImportDeclarations)
391
+ if (node.type === "ImportDeclaration") continue;
392
+
393
+ // VariableDeclaration -- include only if all declarators are safe
394
+ if (node.type === "VariableDeclaration") {
395
+ if (isSafeVariableDeclaration(node, handlerNames)) {
396
+ declarations.push(code.slice(node.start, node.end));
397
+ }
398
+ continue;
399
+ }
400
+
401
+ // FunctionDeclaration -- always safe (body is lazy)
402
+ if (node.type === "FunctionDeclaration") {
403
+ declarations.push(code.slice(node.start, node.end));
404
+ continue;
405
+ }
406
+
407
+ // ExportNamedDeclaration with a declaration inside -- strip the export
408
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
409
+ const decl = node.declaration;
410
+ if (decl.type === "VariableDeclaration") {
411
+ if (isSafeVariableDeclaration(decl, handlerNames)) {
412
+ declarations.push(code.slice(decl.start, decl.end));
413
+ }
414
+ } else if (decl.type === "FunctionDeclaration") {
415
+ declarations.push(code.slice(decl.start, decl.end));
416
+ }
417
+ continue;
418
+ }
419
+
420
+ // Skip: ClassDeclaration (field initializers can reference imports),
421
+ // ExportDefaultDeclaration, ExportAllDeclaration,
422
+ // ExportNamedDeclaration without declaration (re-exports),
423
+ // ExpressionStatement (side effects), etc.
424
+ }
425
+
426
+ return declarations;
427
+ }
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // Transform
431
+ // ---------------------------------------------------------------------------
432
+
433
+ /**
434
+ * Transform inline handler calls by extracting them into virtual modules.
435
+ * Only processes inline calls (exportInfo === null); export const calls are
436
+ * handled by the existing regex fast path.
437
+ *
438
+ * Always extracts (dev and build) to keep server-only imports out of non-RSC
439
+ * environments. The virtual module goes through the standard transform pipeline
440
+ * automatically -- the existing export const regex path handles it.
441
+ *
442
+ * Returns true if any inline calls were transformed.
443
+ */
444
+ export function transformInlineHandlers(
445
+ fnName: string,
446
+ virtualPrefix: string,
447
+ s: MagicString,
448
+ code: string,
449
+ filePath: string,
450
+ virtualRegistry: Map<string, VirtualHandlerEntry>,
451
+ moduleId: string,
452
+ parseAst: (code: string, options?: any) => ProgramNode,
453
+ ): boolean {
454
+ const sites = findHandlerCalls(code, fnName, parseAst);
455
+ const inlineSites = sites.filter((site) => site.exportInfo === null);
456
+ if (inlineSites.length === 0) return false;
457
+
458
+ const imports = extractImportDeclarations(code, parseAst);
459
+
460
+ // Collect local names for both Static and Prerender to exclude their
461
+ // declarations from virtual modules (avoids circular extraction).
462
+ const staticNames = getImportedLocalNames(code, "Static", parseAst);
463
+ const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst);
464
+ const handlerNames = new Set([...staticNames, ...prerenderNames]);
465
+ const declarations = extractModuleLevelDeclarations(
466
+ code,
467
+ parseAst,
468
+ handlerNames,
469
+ );
470
+
471
+ // Track line occurrences for same-line collision handling
472
+ const lineCounts = new Map<number, number>();
473
+
474
+ // Collect all import statements to prepend
475
+ const importStatements: string[] = [];
476
+
477
+ for (const site of inlineSites) {
478
+ const lineCount = lineCounts.get(site.lineNumber) ?? 0;
479
+ lineCounts.set(site.lineNumber, lineCount + 1);
480
+
481
+ const hash = hashInlineId(filePath, site.lineNumber, lineCount);
482
+ const exportName = `__sh_${hash}`;
483
+ const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
484
+
485
+ // Extract the full handler call expression text
486
+ const handlerCode = code.slice(site.callStart, site.callEnd);
487
+
488
+ // Register virtual module
489
+ virtualRegistry.set(virtualId, {
490
+ originalModuleId: moduleId,
491
+ imports,
492
+ declarations,
493
+ handlerCode,
494
+ exportName,
495
+ });
496
+
497
+ // Replace inline call with the import name
498
+ s.overwrite(site.callStart, site.callEnd, exportName);
499
+
500
+ // Build the import specifier for this virtual module
501
+ const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
502
+ importStatements.push(`import { ${exportName} } from "${importId}";`);
503
+ }
504
+
505
+ // Insert imports after directive prologue + existing import block
506
+ if (importStatements.length > 0) {
507
+ const importBlock = importStatements.join("\n") + "\n";
508
+ const insertionPos = findImportInsertionPos(code, parseAst);
509
+ if (insertionPos === 0) {
510
+ s.prepend(importBlock);
511
+ } else {
512
+ s.appendLeft(insertionPos, "\n" + importBlock);
513
+ }
514
+ }
515
+
516
+ return true;
517
+ }
@@ -0,0 +1,36 @@
1
+ import packageJson from "../../../package.json" with { type: "json" };
2
+
3
+ export const rangoVersion: string = packageJson.version;
4
+
5
+ let _bannerPrinted = false;
6
+
7
+ export function printBanner(
8
+ mode: "dev" | "build" | "preview",
9
+ preset: string,
10
+ version: string,
11
+ ): void {
12
+ if (_bannerPrinted) return;
13
+ _bannerPrinted = true;
14
+
15
+ // ANSI codes
16
+ const dim = "\x1b[2m";
17
+ const bold = "\x1b[1m";
18
+ const reset = "\x1b[0m";
19
+
20
+ const banner = `
21
+ ${dim} ✦ ✦ ✧. . .${reset}
22
+ ${dim} ╱${reset} ${bold}╔═╗${reset}${dim} * ╱ ✦ *${reset}
23
+ ${dim} ${reset}${bold}║ ║${reset} ${bold}╔═╗${reset}${dim} * ✧. ╱${reset}
24
+ ${dim} ${reset}${bold}╔╗ ║ ║ ║ ║${reset}${dim} * ╱${reset}
25
+ ${dim} ${reset}${bold}║║ ║ ║ ║ ║ ╦═╗╔═╗╔╗╔╔═╗╔═╗${reset}${dim} ✧ ✦${reset}
26
+ ${dim} ${reset}${bold}═╣║ ║ ╠═╝ ║ ╠╦╝╠═╣║║║║ ╦║ ║${reset}${dim} * ✧${reset}
27
+ ${dim} ${reset}${bold}║╚═╝ ╔═══╝ ╩╚═╩ ╩╝╚╝╚═╝╚═╝${reset}${dim} ✦ . *${reset}
28
+ ${dim} ${reset}${bold}╚══╗ ║${reset}${dim} * RSC Wrangler ✧ ✦${reset}
29
+ ${dim} * ${reset}${bold}║ ╠═${reset}${dim} * ✧. ╱${reset}
30
+ ${bold}══════╝ ╚═════════╩═══${reset}${dim} ✦ *${reset}
31
+
32
+ v${version} · ${preset} · ${mode}
33
+ `;
34
+
35
+ console.log(banner);
36
+ }
@@ -0,0 +1,137 @@
1
+ import {
2
+ skipStringOrComment,
3
+ escapeRegExp,
4
+ } from "../plugins/expose-id-utils.js";
5
+
6
+ /**
7
+ * Find matching close paren in bundled code using depth counting.
8
+ * Uses skipStringOrComment from expose-id-utils to correctly handle
9
+ * template literal ${...} expressions, comments, and nested strings.
10
+ * Returns the position after the closing paren, or -1 if unmatched.
11
+ * @internal Exported for testing only.
12
+ */
13
+ export function findMatchingParenInBundle(
14
+ code: string,
15
+ openParenPos: number,
16
+ ): number {
17
+ let depth = 1;
18
+ let pos = openParenPos;
19
+ while (pos < code.length && depth > 0) {
20
+ const skipped = skipStringOrComment(code, pos);
21
+ if (skipped > pos) {
22
+ pos = skipped;
23
+ continue;
24
+ }
25
+ if (code[pos] === "(") depth++;
26
+ else if (code[pos] === ")") depth--;
27
+ pos++;
28
+ }
29
+ return depth === 0 ? pos : -1;
30
+ }
31
+
32
+ /**
33
+ * Scan a bundled chunk for handler exports of a given type and extract
34
+ * their names + $$id values. Optionally detects passthrough flag.
35
+ * @internal Exported for testing only.
36
+ */
37
+ export function extractHandlerExportsFromChunk(
38
+ chunkCode: string,
39
+ handlerModules: Map<string, string[]>,
40
+ fnName: string,
41
+ detectPassthrough: boolean,
42
+ ): Array<{ name: string; handlerId: string; passthrough: boolean }> {
43
+ const handlers: Array<{
44
+ name: string;
45
+ handlerId: string;
46
+ passthrough: boolean;
47
+ }> = [];
48
+
49
+ for (const [, handlerNames] of handlerModules) {
50
+ for (const name of handlerNames) {
51
+ const eName = escapeRegExp(name);
52
+ const idPattern = new RegExp(
53
+ `(?<![a-zA-Z0-9_])${eName}\\.\\$\\$id\\s*=\\s*"([^"]+)"`,
54
+ );
55
+ const match = chunkCode.match(idPattern);
56
+ if (!match) continue;
57
+
58
+ let isPassthrough = false;
59
+ if (detectPassthrough) {
60
+ const eFnName = escapeRegExp(fnName);
61
+ const callStartRe = new RegExp(
62
+ `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
63
+ );
64
+ const callStart = callStartRe.exec(chunkCode);
65
+ if (callStart) {
66
+ const afterOpen = callStart.index + callStart[0].length;
67
+ const closePos = findMatchingParenInBundle(chunkCode, afterOpen);
68
+ if (closePos !== -1) {
69
+ const callBody = chunkCode.slice(callStart.index, closePos);
70
+ isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
71
+ }
72
+ }
73
+ }
74
+ handlers.push({ name, handlerId: match[1], passthrough: isPassthrough });
75
+ }
76
+ }
77
+
78
+ return handlers;
79
+ }
80
+
81
+ /**
82
+ * Evict handler code from a bundled chunk, replacing full handler call
83
+ * expressions with lightweight stub objects. Returns the modified code
84
+ * and bytes saved, or null if no changes were made.
85
+ * @internal Exported for testing only.
86
+ */
87
+ export function evictHandlerCode(
88
+ code: string,
89
+ exports: Array<{ name: string; handlerId: string; passthrough?: boolean }>,
90
+ fnName: string,
91
+ brand: string,
92
+ ): { code: string; savedBytes: number } | null {
93
+ const originalSize = Buffer.byteLength(code);
94
+ let modified = code;
95
+
96
+ const eFnName = escapeRegExp(fnName);
97
+ for (const { name, handlerId, passthrough } of exports) {
98
+ if (passthrough) continue;
99
+
100
+ const eName = escapeRegExp(name);
101
+ const callStartRe = new RegExp(
102
+ `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
103
+ );
104
+ const startMatch = callStartRe.exec(modified);
105
+ if (!startMatch) continue;
106
+
107
+ const afterOpen = startMatch.index + startMatch[0].length;
108
+ const closePos = findMatchingParenInBundle(modified, afterOpen);
109
+ if (closePos === -1) continue;
110
+
111
+ // Skip trailing whitespace and optional semicolon
112
+ let rangeEnd = closePos;
113
+ while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
114
+ rangeEnd++;
115
+ if (modified[rangeEnd] === ";") rangeEnd++;
116
+
117
+ // Validate: matched range must contain the expected handlerId
118
+ const matched = modified.slice(startMatch.index, rangeEnd);
119
+ if (!matched.includes(handlerId)) continue;
120
+
121
+ const stub = `const ${name} = { __brand: "${brand}", $$id: "${handlerId}" };`;
122
+ modified =
123
+ modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
124
+
125
+ // Remove the now-redundant $$id assignment line.
126
+ modified = modified.replace(
127
+ new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
128
+ "",
129
+ );
130
+ }
131
+
132
+ if (modified === code) return null;
133
+ return {
134
+ code: modified,
135
+ savedBytes: originalSize - Buffer.byteLength(modified),
136
+ };
137
+ }