@rangojs/router 0.10.1 → 0.12.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 (62) hide show
  1. package/dist/testing/vitest.js +1 -1
  2. package/dist/types/browser/partial-update.d.ts +1 -0
  3. package/dist/types/client-urls/navigation.d.ts +11 -0
  4. package/dist/types/client-urls/revalidate-chain.d.ts +33 -0
  5. package/dist/types/client-urls/types.d.ts +41 -14
  6. package/dist/types/client.d.ts +2 -0
  7. package/dist/types/deps/rsc-client.d.ts +1 -0
  8. package/dist/types/deps/rsc.d.ts +1 -1
  9. package/dist/types/deps/ssr.d.ts +1 -1
  10. package/dist/types/index.d.ts +1 -1
  11. package/dist/types/index.rsc.d.ts +1 -1
  12. package/dist/types/router/is-action.d.ts +34 -0
  13. package/dist/types/rsc/types.d.ts +9 -9
  14. package/dist/types/ssr/index.d.ts +27 -4
  15. package/dist/types/testing/flight.d.ts +4 -3
  16. package/dist/types/testing/index.d.ts +3 -1
  17. package/dist/types/testing/run-client-revalidate.d.ts +43 -0
  18. package/dist/types/testing/to-url.d.ts +2 -0
  19. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +2 -0
  20. package/dist/types/testing/vitest.d.ts +2 -1
  21. package/dist/types/types/handler-context.d.ts +12 -5
  22. package/dist/types/types/index.d.ts +1 -1
  23. package/dist/types/vite/plugins/expose-action-id.d.ts +14 -0
  24. package/dist/types/vite/plugins/virtual-entries.d.ts +3 -2
  25. package/dist/vite/index.js +66 -13
  26. package/package.json +8 -3
  27. package/skills/client-urls/SKILL.md +26 -4
  28. package/skills/loader/SKILL.md +1 -0
  29. package/skills/testing/SKILL.md +1 -0
  30. package/skills/testing/setup.md +6 -6
  31. package/skills/typesafety/route-types.md +1 -0
  32. package/src/browser/partial-update.ts +11 -2
  33. package/src/browser/server-action-bridge.ts +9 -2
  34. package/src/cache/cache-runtime.ts +1 -1
  35. package/src/cache/segment-codec.ts +2 -2
  36. package/src/client-urls/navigation.ts +40 -42
  37. package/src/client-urls/revalidate-chain.ts +83 -0
  38. package/src/client-urls/types.ts +41 -13
  39. package/src/client.tsx +5 -0
  40. package/src/deps/rsc-client.ts +8 -0
  41. package/src/deps/rsc.ts +4 -2
  42. package/src/deps/ssr.ts +1 -0
  43. package/src/index.rsc.ts +1 -0
  44. package/src/index.ts +1 -0
  45. package/src/router/is-action.ts +100 -0
  46. package/src/router/revalidation.ts +5 -48
  47. package/src/rsc/handler.ts +3 -3
  48. package/src/rsc/server-action.ts +2 -4
  49. package/src/rsc/types.ts +9 -9
  50. package/src/ssr/index.tsx +132 -51
  51. package/src/testing/flight.ts +4 -3
  52. package/src/testing/index.ts +4 -1
  53. package/src/testing/run-client-revalidate.ts +108 -0
  54. package/src/testing/run-transition-when.ts +1 -3
  55. package/src/testing/to-url.ts +5 -0
  56. package/src/testing/vitest-stubs/plugin-rsc.ts +13 -5
  57. package/src/testing/vitest.ts +3 -2
  58. package/src/types/handler-context.ts +13 -5
  59. package/src/types/index.ts +1 -0
  60. package/src/vite/plugins/expose-action-id.ts +29 -1
  61. package/src/vite/plugins/use-cache-transform.ts +65 -1
  62. package/src/vite/plugins/virtual-entries.ts +14 -11
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import type { Plugin } from "vite";
20
+ import type { ModuleExportMeta } from "@vitejs/plugin-rsc/transforms";
20
21
  import path from "node:path";
21
22
  import MagicString from "magic-string";
22
23
  import { normalizePath, hashId } from "./expose-id-utils.js";
@@ -89,6 +90,13 @@ export function useCacheTransform(): Plugin {
89
90
  return;
90
91
  }
91
92
 
93
+ // plugin-rsc 0.5.34 `matchDirective` does `stmt.directive.match(...)`
94
+ // after `"directive" in node`. Vite/oxc parseAst now emits
95
+ // `directive: null` on ordinary ExpressionStatements, so a file that
96
+ // mixes a `"use cache"` function with a sibling handler whose first
97
+ // statement is an expression throws and the wrap is dropped.
98
+ stripNullDirectiveFields(ast);
99
+
92
100
  const filePath = normalizePath(path.relative(projectRoot, id));
93
101
  const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
94
102
 
@@ -101,6 +109,7 @@ export function useCacheTransform(): Plugin {
101
109
  isBuild,
102
110
  isLayoutOrTemplate,
103
111
  transformWrapExport,
112
+ hasDirective,
104
113
  );
105
114
  }
106
115
 
@@ -131,6 +140,7 @@ function transformFileLevelUseCache(
131
140
  isBuild: boolean,
132
141
  isLayoutOrTemplate: boolean,
133
142
  transformWrapExport: (typeof import("@vitejs/plugin-rsc/transforms"))["transformWrapExport"],
143
+ hasDirective: (typeof import("@vitejs/plugin-rsc/transforms"))["hasDirective"],
134
144
  ) {
135
145
  const unconfirmedExports: string[] = [];
136
146
 
@@ -140,8 +150,18 @@ function transformFileLevelUseCache(
140
150
  return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
141
151
  },
142
152
  rejectNonAsyncFunction: false,
143
- filter: (name: string, meta: { isFunction?: boolean }) => {
153
+ filter: (name: string, meta: ModuleExportMeta) => {
144
154
  if (name === "default" && isLayoutOrTemplate) return false;
155
+ // plugin-rsc 0.5.34 hoists mixed inline `"use server"` out of a
156
+ // file-level `"use cache"` module as `$$hoist_*` exports and rebinds the
157
+ // original name to `registerServerReference($$hoist_*, ...)`. Both are
158
+ // server references, not cached functions. The directive check covers
159
+ // the pre-hoist shape (this plugin seeing the source first).
160
+ if (name.startsWith("$$hoist_")) return false;
161
+ if (isHoistedServerReferenceRebind(meta.valueNode)) return false;
162
+ if (functionHasUseServerDirective(meta.valueNode, hasDirective)) {
163
+ return false;
164
+ }
145
165
  // isFunction is boolean | undefined: true = confirmed function, false =
146
166
  // confirmed non-function, undefined = cannot tell statically (e.g. a
147
167
  // factory/HOF initializer `const x = makeCached(fn)`). Deliberate policy:
@@ -250,6 +270,50 @@ function transformFunctionLevelUseCache(
250
270
  }
251
271
  }
252
272
 
273
+ function stripNullDirectiveFields(node: unknown): void {
274
+ if (!node || typeof node !== "object") return;
275
+ const rec = node as Record<string, unknown>;
276
+ if (rec.type === "ExpressionStatement" && typeof rec.directive !== "string") {
277
+ delete rec.directive;
278
+ }
279
+ for (const value of Object.values(rec)) {
280
+ if (Array.isArray(value)) {
281
+ for (const item of value) stripNullDirectiveFields(item);
282
+ } else if (value && typeof value === "object" && "type" in value) {
283
+ stripNullDirectiveFields(value);
284
+ }
285
+ }
286
+ }
287
+
288
+ function isHoistedServerReferenceRebind(
289
+ valueNode: ModuleExportMeta["valueNode"],
290
+ ): boolean {
291
+ if (!valueNode || valueNode.type !== "CallExpression") return false;
292
+ const first = valueNode.arguments[0];
293
+ return (
294
+ first !== undefined &&
295
+ first.type === "Identifier" &&
296
+ first.name.startsWith("$$hoist_")
297
+ );
298
+ }
299
+
300
+ function functionHasUseServerDirective(
301
+ valueNode: ModuleExportMeta["valueNode"],
302
+ hasDirective: (typeof import("@vitejs/plugin-rsc/transforms"))["hasDirective"],
303
+ ): boolean {
304
+ if (!valueNode || !("body" in valueNode)) return false;
305
+ const { body } = valueNode;
306
+ if (!body || Array.isArray(body) || body.type !== "BlockStatement") {
307
+ return false;
308
+ }
309
+ // plugin-rsc types valueNode with plain estree nodes but hasDirective with
310
+ // its oxc-flavored AST; the flavors differ only in position/extra fields.
311
+ return hasDirective(
312
+ body.body as Parameters<typeof hasDirective>[0],
313
+ "use server",
314
+ );
315
+ }
316
+
253
317
  function findFileLevelDirective(
254
318
  ast: any,
255
319
  ): { start: number; end: number } | null {
@@ -63,18 +63,20 @@ function emitProgressiveChunkSize(value: number): string {
63
63
  /**
64
64
  * Generate the virtual SSR entry. `headScripts` mirrors the rango() plugin
65
65
  * option: "preinit" (default) installs the client-reference preinit hook and
66
- * lets the SSR handlers convert the bootstrap to `bootstrapModules`;
67
- * "preload" omits the hook and pins the handlers to the hint-only strategy.
66
+ * threads `getClientEntryUrl` so Fizz emits `bootstrapModules`;
67
+ * "preload" omits the hook and uses the deprecated inline
68
+ * `loadBootstrapScriptContent` bootstrap.
68
69
  */
69
70
  export function getVirtualEntrySSR(
70
71
  headScripts: HeadScriptsOption = "preinit",
71
72
  progressiveChunkSize?: number,
72
73
  ): string {
73
74
  const preinit = headScripts !== "preload";
74
- // The preload variant drops exactly three preinit-only lines, all built
75
- // here so the template below stays a single unconditional shape.
75
+ // The preload variant drops the preinit-only imports/install and swaps the
76
+ // bootstrap dep, all built here so the template below stays a single
77
+ // unconditional shape.
76
78
  const depsImportNames = preinit
77
- ? "createFromReadableStream,\n setOnClientReference,"
79
+ ? "createFromReadableStream,\n setOnClientReference,\n getClientEntryUrl,"
78
80
  : "createFromReadableStream,";
79
81
  const ssrImportNames = preinit ? "\n installClientReferencePreinit," : "";
80
82
  const install = preinit
@@ -85,6 +87,10 @@ export function getVirtualEntrySSR(
85
87
  installClientReferencePreinit(setOnClientReference);
86
88
  `
87
89
  : "";
90
+ const bootstrapDep = preinit
91
+ ? "getClientEntryUrl,"
92
+ : `loadBootstrapScriptContent: () =>
93
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),`;
88
94
  const hs = JSON.stringify(headScripts);
89
95
  // Emitted into all three handlers: live SSR and shell capture consume it
90
96
  // directly; the resume handler receives it for dep-shape uniformity (resume()
@@ -114,8 +120,7 @@ export const renderHTML = createSSRHandler({
114
120
  renderToReadableStream,
115
121
  injectRSCPayload,
116
122
  headScripts: ${hs},${pcs}
117
- loadBootstrapScriptContent: () =>
118
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
123
+ ${bootstrapDep}
119
124
  });
120
125
 
121
126
  export const captureShellHTML = createShellCaptureHandler({
@@ -125,8 +130,7 @@ export const captureShellHTML = createShellCaptureHandler({
125
130
  prerender,
126
131
  resume,
127
132
  headScripts: ${hs},${pcs}
128
- loadBootstrapScriptContent: () =>
129
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
133
+ ${bootstrapDep}
130
134
  });
131
135
 
132
136
  export const resumeShellHTML = createShellResumeHandler({
@@ -136,8 +140,7 @@ export const resumeShellHTML = createShellResumeHandler({
136
140
  prerender,
137
141
  resume,
138
142
  headScripts: ${hs},${pcs}
139
- loadBootstrapScriptContent: () =>
140
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
143
+ ${bootstrapDep}
141
144
  });
142
145
  `.trim();
143
146
  }