@rangojs/router 0.22.1 → 0.23.0

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 (179) hide show
  1. package/dist/bin/rango.js +8 -3
  2. package/dist/testing/vitest.js +31 -1
  3. package/dist/types/browser/defer.d.ts +48 -0
  4. package/dist/types/browser/document-revealed.d.ts +17 -0
  5. package/dist/types/browser/event-controller.d.ts +7 -1
  6. package/dist/types/browser/navigation-transaction.d.ts +21 -7
  7. package/dist/types/browser/partial-update.d.ts +34 -5
  8. package/dist/types/browser/react/NavigationProvider.d.ts +13 -3
  9. package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
  10. package/dist/types/browser/response-adapter.d.ts +7 -0
  11. package/dist/types/browser/types.d.ts +19 -9
  12. package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
  13. package/dist/types/build/generate-route-types.d.ts +1 -1
  14. package/dist/types/build/route-types/router-processing.d.ts +1 -1
  15. package/dist/types/build/route-types/scan-filter.d.ts +16 -2
  16. package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
  17. package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
  18. package/dist/types/client-urls/client-urls.d.ts +1 -1
  19. package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
  20. package/dist/types/client-urls/navigation.d.ts +0 -37
  21. package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
  22. package/dist/types/client-urls/server-projection.d.ts +29 -1
  23. package/dist/types/client-urls/types.d.ts +34 -69
  24. package/dist/types/client.d.ts +0 -1
  25. package/dist/types/errors.d.ts +5 -0
  26. package/dist/types/handles/deferred-resolution.d.ts +2 -1
  27. package/dist/types/handles/pushed-by.d.ts +23 -0
  28. package/dist/types/loader-store.d.ts +8 -0
  29. package/dist/types/prefetch-markers.d.ts +11 -0
  30. package/dist/types/route-definition/helpers-types.d.ts +27 -0
  31. package/dist/types/router/is-action.d.ts +2 -1
  32. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
  33. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +241 -0
  34. package/dist/types/server/context.d.ts +21 -1
  35. package/dist/types/server/handle-store.d.ts +9 -1
  36. package/dist/types/server/request-context.d.ts +14 -1
  37. package/dist/types/server.d.ts +1 -0
  38. package/dist/types/testing/flight.entry.d.ts +1 -1
  39. package/dist/types/testing/index.d.ts +3 -3
  40. package/dist/types/testing/internal/action-id.d.ts +10 -1
  41. package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
  42. package/dist/types/testing/internal/test-action.d.ts +8 -0
  43. package/dist/types/testing/run-loader.d.ts +2 -1
  44. package/dist/types/testing/run-revalidate.d.ts +74 -0
  45. package/dist/types/testing/run-transition-when.d.ts +2 -3
  46. package/dist/types/testing/serve-shell-request.d.ts +38 -0
  47. package/dist/types/testing/vitest.d.ts +11 -0
  48. package/dist/types/types/boundaries.d.ts +9 -0
  49. package/dist/types/types/handler-context.d.ts +14 -0
  50. package/dist/types/types/loader-types.d.ts +30 -0
  51. package/dist/types/types/segments.d.ts +21 -4
  52. package/dist/types/urls/path-helper-types.d.ts +25 -4
  53. package/dist/types/urls/type-extraction.d.ts +6 -2
  54. package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
  55. package/dist/types/vite/discovery/state.d.ts +11 -4
  56. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
  57. package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
  58. package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
  59. package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
  60. package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
  61. package/dist/types/vite/router-discovery.d.ts +10 -0
  62. package/dist/vite/index.js +842 -308
  63. package/package.json +1 -1
  64. package/skills/caching/SKILL.md +42 -5
  65. package/skills/catalog.json +2 -2
  66. package/skills/client-urls/SKILL.md +113 -24
  67. package/skills/cloudflare/SKILL.md +14 -9
  68. package/skills/hooks/SKILL.md +11 -3
  69. package/skills/links/SKILL.md +24 -0
  70. package/skills/loader/SKILL.md +192 -1
  71. package/skills/migrate-nextjs/SKILL.md +2 -2
  72. package/skills/migrate-react-router/SKILL.md +10 -7
  73. package/skills/migrate-react-router/data-and-actions.md +1 -1
  74. package/skills/ppr/SKILL.md +13 -0
  75. package/skills/prerender/SKILL.md +22 -8
  76. package/skills/rango/SKILL.md +12 -6
  77. package/skills/route/SKILL.md +139 -0
  78. package/skills/testing/SKILL.md +3 -2
  79. package/skills/testing/client-components.md +1 -1
  80. package/skills/testing/predicates.md +67 -32
  81. package/skills/testing/serve-shell-request.md +88 -18
  82. package/skills/testing/setup.md +2 -2
  83. package/skills/typesafety/route-types.md +2 -2
  84. package/skills/view-transitions/SKILL.md +26 -0
  85. package/src/browser/defer.ts +82 -0
  86. package/src/browser/document-revealed.ts +52 -0
  87. package/src/browser/event-controller.ts +43 -32
  88. package/src/browser/navigation-bridge.ts +64 -14
  89. package/src/browser/navigation-client.ts +48 -36
  90. package/src/browser/navigation-store.ts +13 -0
  91. package/src/browser/navigation-transaction.ts +25 -8
  92. package/src/browser/partial-update.ts +528 -135
  93. package/src/browser/prefetch/fetch.ts +2 -1
  94. package/src/browser/react/NavigationProvider.tsx +47 -28
  95. package/src/browser/react/filter-segment-order.ts +13 -2
  96. package/src/browser/response-adapter.ts +20 -0
  97. package/src/browser/rsc-router.tsx +17 -45
  98. package/src/browser/server-action-bridge.ts +19 -63
  99. package/src/browser/types.ts +19 -9
  100. package/src/browser/validate-redirect-origin.ts +16 -0
  101. package/src/build/generate-route-types.ts +1 -0
  102. package/src/build/route-types/router-processing.ts +3 -6
  103. package/src/build/route-types/scan-filter.ts +37 -10
  104. package/src/cache/cf/cf-cache-store.ts +59 -21
  105. package/src/cache/cf/cf-cache-types.ts +24 -2
  106. package/src/cache/document-cache.ts +22 -1
  107. package/src/client-urls/client-urls.ts +22 -6
  108. package/src/client-urls/hoisted-revalidate.ts +25 -0
  109. package/src/client-urls/navigation.ts +0 -96
  110. package/src/client-urls/revalidate-registry.ts +30 -0
  111. package/src/client-urls/server-projection.ts +151 -65
  112. package/src/client-urls/types.ts +39 -71
  113. package/src/client.tsx +9 -5
  114. package/src/errors.ts +15 -0
  115. package/src/handles/deferred-resolution.ts +28 -5
  116. package/src/handles/pushed-by.ts +122 -0
  117. package/src/loader-store.ts +18 -7
  118. package/src/prefetch-markers.ts +13 -0
  119. package/src/route-content-wrapper.tsx +18 -4
  120. package/src/route-definition/dsl-helpers.ts +27 -4
  121. package/src/route-definition/helpers-types.ts +27 -1
  122. package/src/router/intercept-resolution.ts +6 -4
  123. package/src/router/is-action.ts +2 -1
  124. package/src/router/loader-resolution.ts +83 -4
  125. package/src/router/match-api.ts +36 -1
  126. package/src/router/match-middleware/background-revalidation.ts +11 -1
  127. package/src/router/match-middleware/cache-lookup.ts +29 -6
  128. package/src/router/match-middleware/cache-store.ts +17 -1
  129. package/src/router/match-result.ts +14 -2
  130. package/src/router/navigation-snapshot.ts +2 -1
  131. package/src/router/segment-resolution/fresh.ts +22 -18
  132. package/src/router/segment-resolution/loader-cache.ts +2 -1
  133. package/src/router/segment-resolution/loader-mask.ts +5 -3
  134. package/src/router/segment-resolution/prefetch-deferral.ts +543 -0
  135. package/src/router/segment-resolution/revalidation.ts +169 -60
  136. package/src/rsc/rsc-rendering.ts +50 -7
  137. package/src/rsc/shell-capture.ts +2 -1
  138. package/src/segment-loader-promise.ts +23 -20
  139. package/src/segment-system.tsx +60 -26
  140. package/src/server/context.ts +39 -5
  141. package/src/server/handle-store.ts +104 -8
  142. package/src/server/request-context.ts +21 -1
  143. package/src/server.ts +4 -0
  144. package/src/testing/action-form.ts +2 -11
  145. package/src/testing/e2e/page-helpers.ts +3 -1
  146. package/src/testing/flight.entry.ts +1 -0
  147. package/src/testing/index.ts +6 -3
  148. package/src/testing/internal/action-id.ts +16 -1
  149. package/src/testing/internal/client-urls-projection.ts +70 -0
  150. package/src/testing/internal/test-action.ts +25 -0
  151. package/src/testing/run-loader.ts +2 -1
  152. package/src/testing/run-revalidate.ts +142 -0
  153. package/src/testing/run-transition-when.ts +7 -8
  154. package/src/testing/serve-shell-request.ts +116 -6
  155. package/src/testing/vitest.ts +67 -1
  156. package/src/types/boundaries.ts +12 -0
  157. package/src/types/handler-context.ts +14 -0
  158. package/src/types/loader-types.ts +30 -0
  159. package/src/types/segments.ts +26 -4
  160. package/src/urls/path-helper-types.ts +27 -5
  161. package/src/urls/type-extraction.ts +30 -10
  162. package/src/use-loader.tsx +18 -13
  163. package/src/vite/discovery/client-urls-projection.ts +20 -21
  164. package/src/vite/discovery/discover-routers.ts +94 -26
  165. package/src/vite/discovery/state.ts +20 -4
  166. package/src/vite/discovery/virtual-module-codegen.ts +22 -17
  167. package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
  168. package/src/vite/plugins/hoist/literal.ts +417 -0
  169. package/src/vite/plugins/hoist/scope.ts +370 -0
  170. package/src/vite/plugins/hoist/source.ts +84 -0
  171. package/src/vite/plugins/transition-when-hoist.ts +113 -779
  172. package/src/vite/rango.ts +2 -0
  173. package/src/vite/router-discovery.ts +93 -32
  174. package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
  175. package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
  176. package/dist/types/testing/run-client-revalidate.d.ts +0 -43
  177. package/src/client-urls/revalidate-chain.ts +0 -83
  178. package/src/client-urls/revalidation-protocol.ts +0 -56
  179. package/src/testing/run-client-revalidate.ts +0 -92
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Scope analysis shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts, client-urls-revalidate-hoist.ts): a module's
4
+ * scope tree, every identifier reference, and its imports. Enough of ES
5
+ * scoping for the free-variable analysis of one literal.
6
+ */
7
+
8
+ // oxc ESTree nodes; the hoists read a handful of fields off each.
9
+ export type Node = any;
10
+
11
+ export interface ImportBinding {
12
+ readonly source: string;
13
+ /** "default", "*", or the imported name. */
14
+ readonly imported: string;
15
+ readonly typeOnly: boolean;
16
+ }
17
+
18
+ export class Scope {
19
+ readonly declarations: Set<string> = new Set<string>();
20
+ constructor(
21
+ readonly parent: Scope | null,
22
+ readonly isFunction: boolean,
23
+ readonly node: Node,
24
+ ) {}
25
+ nearestFunction(): Scope {
26
+ return this.isFunction || !this.parent
27
+ ? this
28
+ : this.parent.nearestFunction();
29
+ }
30
+ lookup(name: string): Scope | null {
31
+ if (this.declarations.has(name)) return this;
32
+ return this.parent ? this.parent.lookup(name) : null;
33
+ }
34
+ }
35
+
36
+ export interface Reference {
37
+ readonly node: Node;
38
+ readonly scope: Scope;
39
+ readonly typeOnly: boolean;
40
+ }
41
+
42
+ export const TYPE_KEYS: ReadonlySet<string> = new Set([
43
+ "typeAnnotation",
44
+ "returnType",
45
+ "typeParameters",
46
+ "typeArguments",
47
+ "superTypeArguments",
48
+ "implements",
49
+ ]);
50
+ /**
51
+ * The identifier a JSX element name refers to: a component (`<Local />`) or
52
+ * the root of a member name (`<ui.Badge />`). Lowercase and dashed names are
53
+ * intrinsic tags, not references; `<this.X />` is checked as `this`.
54
+ */
55
+ function jsxNameReference(name: Node): Node | null {
56
+ if (name?.type === "JSXIdentifier") {
57
+ return /^[a-z]/.test(name.name) || name.name.includes("-") ? null : name;
58
+ }
59
+ let root = name;
60
+ while (root?.type === "JSXMemberExpression") root = root.object;
61
+ return root?.type === "JSXIdentifier" && root.name !== "this" ? root : null;
62
+ }
63
+
64
+ function patternNames(pattern: Node, names: string[] = []): string[] {
65
+ if (!pattern) return names;
66
+ switch (pattern.type) {
67
+ case "Identifier":
68
+ names.push(pattern.name);
69
+ break;
70
+ case "ObjectPattern":
71
+ for (const prop of pattern.properties) {
72
+ patternNames(prop.type === "RestElement" ? prop : prop.value, names);
73
+ }
74
+ break;
75
+ case "ArrayPattern":
76
+ for (const element of pattern.elements) patternNames(element, names);
77
+ break;
78
+ case "RestElement":
79
+ patternNames(pattern.argument, names);
80
+ break;
81
+ case "AssignmentPattern":
82
+ patternNames(pattern.left, names);
83
+ break;
84
+ case "TSParameterProperty":
85
+ patternNames(pattern.parameter, names);
86
+ break;
87
+ }
88
+ return names;
89
+ }
90
+
91
+ /**
92
+ * Scope tree and every identifier reference of a module, TS type positions
93
+ * flagged. Enough of ES scoping for free-variable analysis of one literal:
94
+ * var hoists to the function scope, let/const/class to the block, function
95
+ * declarations to their block.
96
+ */
97
+ export function analyzeScopes(program: Node): {
98
+ moduleScope: Scope;
99
+ references: Reference[];
100
+ scopeOf: Map<Node, Scope>;
101
+ } {
102
+ const moduleScope = new Scope(null, true, program);
103
+ const scopeOf = new Map<Node, Scope>([[program, moduleScope]]);
104
+ const references: Reference[] = [];
105
+
106
+ const declarePattern = (pattern: Node, scope: Scope): void => {
107
+ for (const name of patternNames(pattern)) scope.declarations.add(name);
108
+ };
109
+
110
+ // Binding patterns: identifiers declare; defaults and computed keys refer.
111
+ const walkPattern = (pattern: Node, scope: Scope, typeOnly: boolean) => {
112
+ if (!pattern) return;
113
+ switch (pattern.type) {
114
+ case "Identifier":
115
+ if (pattern.typeAnnotation) walk(pattern.typeAnnotation, scope, true);
116
+ return;
117
+ case "ObjectPattern":
118
+ for (const prop of pattern.properties) {
119
+ if (prop.type === "RestElement") {
120
+ walkPattern(prop.argument, scope, typeOnly);
121
+ } else {
122
+ if (prop.computed) walk(prop.key, scope, typeOnly);
123
+ walkPattern(prop.value, scope, typeOnly);
124
+ }
125
+ }
126
+ if (pattern.typeAnnotation) walk(pattern.typeAnnotation, scope, true);
127
+ return;
128
+ case "ArrayPattern":
129
+ for (const element of pattern.elements) {
130
+ walkPattern(element, scope, typeOnly);
131
+ }
132
+ if (pattern.typeAnnotation) walk(pattern.typeAnnotation, scope, true);
133
+ return;
134
+ case "RestElement":
135
+ walkPattern(pattern.argument, scope, typeOnly);
136
+ return;
137
+ case "AssignmentPattern":
138
+ walkPattern(pattern.left, scope, typeOnly);
139
+ walk(pattern.right, scope, typeOnly);
140
+ return;
141
+ case "TSParameterProperty":
142
+ walkPattern(pattern.parameter, scope, typeOnly);
143
+ return;
144
+ default:
145
+ walk(pattern, scope, typeOnly);
146
+ }
147
+ };
148
+
149
+ // Hoist function-scoped and block-scoped declarations of a body before its
150
+ // statements are walked, so a reference ahead of its declaration resolves.
151
+ const hoistDeclarations = (statements: Node[], scope: Scope): void => {
152
+ for (const statement of statements) {
153
+ const node =
154
+ statement.type === "ExportNamedDeclaration" ||
155
+ statement.type === "ExportDefaultDeclaration"
156
+ ? statement.declaration
157
+ : statement;
158
+ if (!node) continue;
159
+ switch (node.type) {
160
+ case "ImportDeclaration":
161
+ for (const spec of node.specifiers) {
162
+ scope.declarations.add(spec.local.name);
163
+ }
164
+ break;
165
+ case "FunctionDeclaration":
166
+ case "ClassDeclaration":
167
+ case "TSEnumDeclaration":
168
+ case "TSTypeAliasDeclaration":
169
+ case "TSInterfaceDeclaration":
170
+ case "TSModuleDeclaration":
171
+ if (node.id?.type === "Identifier") {
172
+ scope.declarations.add(node.id.name);
173
+ }
174
+ break;
175
+ case "VariableDeclaration":
176
+ for (const declarator of node.declarations) {
177
+ declarePattern(
178
+ declarator.id,
179
+ node.kind === "var" ? scope.nearestFunction() : scope,
180
+ );
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ };
186
+
187
+ const walk = (node: Node, scope: Scope, typeOnly: boolean): void => {
188
+ if (!node || typeof node !== "object") return;
189
+ if (Array.isArray(node)) {
190
+ for (const item of node) walk(item, scope, typeOnly);
191
+ return;
192
+ }
193
+ if (typeof node.type !== "string") return;
194
+
195
+ switch (node.type) {
196
+ case "Identifier":
197
+ references.push({ node, scope, typeOnly });
198
+ if (node.typeAnnotation) walk(node.typeAnnotation, scope, true);
199
+ return;
200
+ case "ImportDeclaration":
201
+ case "ExportAllDeclaration":
202
+ return;
203
+ case "ExportNamedDeclaration":
204
+ if (node.declaration) walk(node.declaration, scope, typeOnly);
205
+ else if (!node.source) {
206
+ for (const spec of node.specifiers) {
207
+ walk(spec.local, scope, typeOnly || node.exportKind === "type");
208
+ }
209
+ }
210
+ return;
211
+ case "MemberExpression":
212
+ walk(node.object, scope, typeOnly);
213
+ if (node.computed) walk(node.property, scope, typeOnly);
214
+ return;
215
+ case "Property":
216
+ case "MethodDefinition":
217
+ case "PropertyDefinition":
218
+ case "TSPropertySignature":
219
+ case "TSMethodSignature":
220
+ if (node.computed) walk(node.key, scope, typeOnly);
221
+ walk(node.value, scope, typeOnly);
222
+ if (node.typeAnnotation) walk(node.typeAnnotation, scope, true);
223
+ return;
224
+ case "LabeledStatement":
225
+ walk(node.body, scope, typeOnly);
226
+ return;
227
+ case "BreakStatement":
228
+ case "ContinueStatement":
229
+ case "MetaProperty":
230
+ return;
231
+ case "JSXOpeningElement":
232
+ case "JSXClosingElement": {
233
+ const ref = jsxNameReference(node.name);
234
+ if (ref) references.push({ node: ref, scope, typeOnly });
235
+ walk(node.typeArguments, scope, true);
236
+ walk(node.attributes, scope, typeOnly);
237
+ return;
238
+ }
239
+ case "TSQualifiedName":
240
+ walk(node.left, scope, typeOnly);
241
+ return;
242
+ case "TSEnumMember":
243
+ walk(node.initializer, scope, typeOnly);
244
+ return;
245
+ case "TSTypeAliasDeclaration":
246
+ case "TSInterfaceDeclaration":
247
+ walk(node.typeParameters, scope, true);
248
+ walk(node.typeAnnotation ?? node.body, scope, true);
249
+ walk(node.extends, scope, true);
250
+ return;
251
+ case "VariableDeclarator":
252
+ walkPattern(node.id, scope, typeOnly);
253
+ walk(node.init, scope, typeOnly);
254
+ return;
255
+ case "FunctionDeclaration":
256
+ case "FunctionExpression":
257
+ case "ArrowFunctionExpression": {
258
+ const fnScope = new Scope(scope, true, node);
259
+ scopeOf.set(node, fnScope);
260
+ if (node.type === "FunctionExpression" && node.id) {
261
+ fnScope.declarations.add(node.id.name);
262
+ }
263
+ for (const param of node.params) declarePattern(param, fnScope);
264
+ walk(node.typeParameters, fnScope, true);
265
+ for (const param of node.params) walkPattern(param, fnScope, typeOnly);
266
+ walk(node.returnType, fnScope, true);
267
+ if (node.body?.type === "BlockStatement") {
268
+ hoistDeclarations(node.body.body, fnScope);
269
+ walk(node.body.body, fnScope, typeOnly);
270
+ } else {
271
+ walk(node.body, fnScope, typeOnly);
272
+ }
273
+ return;
274
+ }
275
+ case "ClassDeclaration":
276
+ case "ClassExpression": {
277
+ let classScope = scope;
278
+ if (node.type === "ClassExpression" && node.id) {
279
+ classScope = new Scope(scope, false, node);
280
+ classScope.declarations.add(node.id.name);
281
+ scopeOf.set(node, classScope);
282
+ }
283
+ walk(node.superClass, classScope, typeOnly);
284
+ walk(node.superTypeArguments, classScope, true);
285
+ walk(node.typeParameters, classScope, true);
286
+ walk(node.implements, classScope, true);
287
+ walk(node.body, classScope, typeOnly);
288
+ return;
289
+ }
290
+ case "BlockStatement":
291
+ case "StaticBlock": {
292
+ const blockScope = new Scope(scope, false, node);
293
+ scopeOf.set(node, blockScope);
294
+ hoistDeclarations(node.body, blockScope);
295
+ walk(node.body, blockScope, typeOnly);
296
+ return;
297
+ }
298
+ case "ForStatement":
299
+ case "ForInStatement":
300
+ case "ForOfStatement": {
301
+ const loopScope = new Scope(scope, false, node);
302
+ scopeOf.set(node, loopScope);
303
+ const head = node.type === "ForStatement" ? node.init : node.left;
304
+ if (head?.type === "VariableDeclaration") {
305
+ hoistDeclarations([head], loopScope);
306
+ }
307
+ for (const key of ["init", "left", "right", "test", "update", "body"]) {
308
+ walk(node[key], loopScope, typeOnly);
309
+ }
310
+ return;
311
+ }
312
+ case "SwitchStatement": {
313
+ walk(node.discriminant, scope, typeOnly);
314
+ const switchScope = new Scope(scope, false, node);
315
+ scopeOf.set(node, switchScope);
316
+ hoistDeclarations(
317
+ node.cases.flatMap((c: Node) => c.consequent),
318
+ switchScope,
319
+ );
320
+ for (const c of node.cases) {
321
+ walk(c.test, switchScope, typeOnly);
322
+ walk(c.consequent, switchScope, typeOnly);
323
+ }
324
+ return;
325
+ }
326
+ case "CatchClause": {
327
+ const catchScope = new Scope(scope, false, node);
328
+ scopeOf.set(node, catchScope);
329
+ if (node.param) {
330
+ declarePattern(node.param, catchScope);
331
+ walkPattern(node.param, catchScope, typeOnly);
332
+ }
333
+ walk(node.body, catchScope, typeOnly);
334
+ return;
335
+ }
336
+ }
337
+
338
+ for (const key of Object.keys(node)) {
339
+ if (key === "type" || key === "start" || key === "end") continue;
340
+ walk(node[key], scope, typeOnly || TYPE_KEYS.has(key));
341
+ }
342
+ };
343
+
344
+ hoistDeclarations(program.body, moduleScope);
345
+ walk(program.body, moduleScope, false);
346
+ return { moduleScope, references, scopeOf };
347
+ }
348
+
349
+ export function collectImports(program: Node): Map<string, ImportBinding> {
350
+ const imports = new Map<string, ImportBinding>();
351
+ for (const statement of program.body) {
352
+ if (statement.type !== "ImportDeclaration") continue;
353
+ const declTypeOnly = statement.importKind === "type";
354
+ for (const spec of statement.specifiers) {
355
+ imports.set(spec.local.name, {
356
+ source: statement.source.value,
357
+ imported:
358
+ spec.type === "ImportDefaultSpecifier"
359
+ ? "default"
360
+ : spec.type === "ImportNamespaceSpecifier"
361
+ ? "*"
362
+ : spec.imported.type === "Identifier"
363
+ ? spec.imported.name
364
+ : String(spec.imported.value),
365
+ typeOnly: declTypeOnly || spec.importKind === "type",
366
+ });
367
+ }
368
+ }
369
+ return imports;
370
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Source reading shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts, client-urls-revalidate-hoist.ts): which ids are
4
+ * source files, reading one off disk, resolving an import to the file it
5
+ * names, and the "emitted or disk" lookup behind their `?rango-*` modules.
6
+ */
7
+
8
+ import { readFile } from "node:fs/promises";
9
+ import { normalizePath } from "vite";
10
+
11
+ /** A file path (query already stripped) a hoist transform may rewrite. */
12
+ export const SOURCE_FILE_RE: RegExp = /\.[cm]?[jt]sx?$/;
13
+
14
+ export async function readSource(file: string): Promise<string | null> {
15
+ try {
16
+ return await readFile(file, "utf-8");
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ /** The part of a plugin context {@link resolveSourceTarget} needs. */
23
+ export interface SourceResolver {
24
+ resolve(
25
+ source: string,
26
+ importer?: string,
27
+ ): Promise<{ id: string; external?: boolean | string } | null>;
28
+ }
29
+
30
+ /**
31
+ * The file `source` names when imported from `importer`, query stripped, or
32
+ * null when it is external or virtual. `skipNodeModules` also yields null for
33
+ * a dependency: transition-when-hoist only looks for `import "server-only"`
34
+ * in project files, while client-urls-revalidate-hoist must see the
35
+ * "use client" directive of a package's modules too.
36
+ */
37
+ export async function resolveSourceTarget(
38
+ ctx: SourceResolver,
39
+ source: string,
40
+ importer: string,
41
+ options: { readonly skipNodeModules: boolean },
42
+ ): Promise<string | null> {
43
+ const resolved = await ctx.resolve(source, importer);
44
+ if (!resolved || resolved.external) return null;
45
+ const target = resolved.id.split("?", 1)[0]!;
46
+ if (target.startsWith("\0")) return null;
47
+ if (options.skipNodeModules && target.includes("/node_modules/")) {
48
+ return null;
49
+ }
50
+ return target;
51
+ }
52
+
53
+ /** What a hoist's RSC transform emitted for a file, and the disk source it saw. */
54
+ export interface EmittedEntry<T> {
55
+ readonly disk: string | null;
56
+ readonly value: T;
57
+ }
58
+
59
+ export async function recordEmitted<T>(
60
+ emitted: Map<string, EmittedEntry<T>>,
61
+ file: string,
62
+ value: T,
63
+ ): Promise<void> {
64
+ emitted.set(file, { disk: await readSource(file), value });
65
+ }
66
+
67
+ /**
68
+ * What a `?rango-*` load serves for `hoistedFile`: the transform's emission
69
+ * when it saw the current disk source (or the file is gone), else
70
+ * `rehoist(disk)`. The transform hoists the pipeline code, which an earlier
71
+ * plugin may have changed, so re-hoisting the disk source is the fallback only
72
+ * for an environment loading before the RSC transform ran or for an edit the
73
+ * RSC environment has not re-transformed yet. Undefined when neither exists.
74
+ */
75
+ export async function loadEmittedOrDisk<T>(
76
+ emitted: ReadonlyMap<string, EmittedEntry<T>>,
77
+ hoistedFile: string,
78
+ rehoist: (disk: string) => Promise<T | undefined>,
79
+ ): Promise<T | undefined> {
80
+ const disk = await readSource(hoistedFile);
81
+ const entry = emitted.get(normalizePath(hoistedFile));
82
+ if (entry && (disk === null || entry.disk === disk)) return entry.value;
83
+ return disk === null ? undefined : rehoist(disk);
84
+ }