vinext 0.0.32 → 0.0.34

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 (95) hide show
  1. package/README.md +7 -6
  2. package/dist/config/next-config.d.ts +2 -0
  3. package/dist/config/next-config.js +4 -0
  4. package/dist/config/next-config.js.map +1 -1
  5. package/dist/deploy.js +52 -4
  6. package/dist/deploy.js.map +1 -1
  7. package/dist/entries/app-browser-entry.js +3 -330
  8. package/dist/entries/app-browser-entry.js.map +1 -1
  9. package/dist/entries/app-rsc-entry.js +444 -1265
  10. package/dist/entries/app-rsc-entry.js.map +1 -1
  11. package/dist/entries/app-ssr-entry.js +4 -460
  12. package/dist/entries/app-ssr-entry.js.map +1 -1
  13. package/dist/entries/pages-server-entry.js +8 -1
  14. package/dist/entries/pages-server-entry.js.map +1 -1
  15. package/dist/entries/runtime-entry-module.d.ts +13 -0
  16. package/dist/entries/runtime-entry-module.js +27 -0
  17. package/dist/entries/runtime-entry-module.js.map +1 -0
  18. package/dist/index.js +302 -23
  19. package/dist/index.js.map +1 -1
  20. package/dist/plugins/optimize-imports.d.ts +38 -0
  21. package/dist/plugins/optimize-imports.js +557 -0
  22. package/dist/plugins/optimize-imports.js.map +1 -0
  23. package/dist/server/app-browser-entry.d.ts +1 -0
  24. package/dist/server/app-browser-entry.js +160 -0
  25. package/dist/server/app-browser-entry.js.map +1 -0
  26. package/dist/server/app-browser-stream.d.ts +33 -0
  27. package/dist/server/app-browser-stream.js +54 -0
  28. package/dist/server/app-browser-stream.js.map +1 -0
  29. package/dist/server/app-page-boundary-render.d.ts +63 -0
  30. package/dist/server/app-page-boundary-render.js +182 -0
  31. package/dist/server/app-page-boundary-render.js.map +1 -0
  32. package/dist/server/app-page-boundary.d.ts +57 -0
  33. package/dist/server/app-page-boundary.js +60 -0
  34. package/dist/server/app-page-boundary.js.map +1 -0
  35. package/dist/server/app-page-cache.d.ts +61 -0
  36. package/dist/server/app-page-cache.js +133 -0
  37. package/dist/server/app-page-cache.js.map +1 -0
  38. package/dist/server/app-page-execution.d.ts +46 -0
  39. package/dist/server/app-page-execution.js +109 -0
  40. package/dist/server/app-page-execution.js.map +1 -0
  41. package/dist/server/app-page-probe.d.ts +17 -0
  42. package/dist/server/app-page-probe.js +35 -0
  43. package/dist/server/app-page-probe.js.map +1 -0
  44. package/dist/server/app-page-render.d.ts +59 -0
  45. package/dist/server/app-page-render.js +174 -0
  46. package/dist/server/app-page-render.js.map +1 -0
  47. package/dist/server/app-page-request.d.ts +58 -0
  48. package/dist/server/app-page-request.js +79 -0
  49. package/dist/server/app-page-request.js.map +1 -0
  50. package/dist/server/app-page-response.d.ts +51 -0
  51. package/dist/server/app-page-response.js +90 -0
  52. package/dist/server/app-page-response.js.map +1 -0
  53. package/dist/server/app-page-stream.d.ts +55 -0
  54. package/dist/server/app-page-stream.js +65 -0
  55. package/dist/server/app-page-stream.js.map +1 -0
  56. package/dist/server/app-route-handler-cache.d.ts +42 -0
  57. package/dist/server/app-route-handler-cache.js +69 -0
  58. package/dist/server/app-route-handler-cache.js.map +1 -0
  59. package/dist/server/app-route-handler-execution.d.ts +64 -0
  60. package/dist/server/app-route-handler-execution.js +100 -0
  61. package/dist/server/app-route-handler-execution.js.map +1 -0
  62. package/dist/server/app-route-handler-policy.d.ts +51 -0
  63. package/dist/server/app-route-handler-policy.js +57 -0
  64. package/dist/server/app-route-handler-policy.js.map +1 -0
  65. package/dist/server/app-route-handler-response.d.ts +26 -0
  66. package/dist/server/app-route-handler-response.js +61 -0
  67. package/dist/server/app-route-handler-response.js.map +1 -0
  68. package/dist/server/app-route-handler-runtime.d.ts +27 -0
  69. package/dist/server/app-route-handler-runtime.js +99 -0
  70. package/dist/server/app-route-handler-runtime.js.map +1 -0
  71. package/dist/server/app-ssr-entry.d.ts +19 -0
  72. package/dist/server/app-ssr-entry.js +105 -0
  73. package/dist/server/app-ssr-entry.js.map +1 -0
  74. package/dist/server/app-ssr-stream.d.ts +30 -0
  75. package/dist/server/app-ssr-stream.js +116 -0
  76. package/dist/server/app-ssr-stream.js.map +1 -0
  77. package/dist/server/prod-server.d.ts +13 -1
  78. package/dist/server/prod-server.js +113 -19
  79. package/dist/server/prod-server.js.map +1 -1
  80. package/dist/server/worker-utils.d.ts +0 -6
  81. package/dist/server/worker-utils.js +41 -5
  82. package/dist/server/worker-utils.js.map +1 -1
  83. package/dist/shims/error-boundary.js +1 -1
  84. package/dist/shims/font-google-base.js +1 -1
  85. package/dist/shims/font-google-base.js.map +1 -1
  86. package/dist/shims/font-google.d.ts +2 -3
  87. package/dist/shims/font-google.js +2 -3
  88. package/dist/shims/metadata.js +3 -3
  89. package/dist/shims/metadata.js.map +1 -1
  90. package/dist/shims/request-state-types.d.ts +2 -2
  91. package/dist/shims/unified-request-context.d.ts +1 -1
  92. package/package.json +1 -1
  93. package/dist/shims/font-google.generated.d.ts +0 -1929
  94. package/dist/shims/font-google.generated.js +0 -1929
  95. package/dist/shims/font-google.generated.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { resolveRuntimeEntryModule } from "./runtime-entry-module.js";
1
2
  //#region src/entries/app-browser-entry.ts
2
3
  /**
3
4
  * Generate the virtual browser entry module.
@@ -7,336 +8,8 @@
7
8
  * RSC streams.
8
9
  */
9
10
  function generateBrowserEntry() {
10
- return `
11
- import {
12
- createFromReadableStream,
13
- createFromFetch,
14
- setServerCallback,
15
- encodeReply,
16
- createTemporaryReferenceSet,
17
- } from "@vitejs/plugin-rsc/browser";
18
- import { hydrateRoot } from "react-dom/client";
19
- import { flushSync } from "react-dom";
20
- import { setClientParams, setNavigationContext, toRscUrl, getPrefetchCache, getPrefetchedUrls, PREFETCH_CACHE_TTL } from "next/navigation";
21
-
22
- let reactRoot;
23
-
24
- /**
25
- * Convert the embedded RSC chunks back to a ReadableStream.
26
- * Each chunk is a text string that needs to be encoded back to Uint8Array.
27
- */
28
- function chunksToReadableStream(chunks) {
29
- const encoder = new TextEncoder();
30
- return new ReadableStream({
31
- start(controller) {
32
- for (const chunk of chunks) {
33
- controller.enqueue(encoder.encode(chunk));
34
- }
35
- controller.close();
36
- }
37
- });
38
- }
39
-
40
- /**
41
- * Create a ReadableStream from progressively-embedded RSC chunks.
42
- * The server injects RSC data as <script> tags that push to
43
- * self.__VINEXT_RSC_CHUNKS__ throughout the HTML stream, and sets
44
- * self.__VINEXT_RSC_DONE__ = true when complete.
45
- *
46
- * Instead of polling with setTimeout, we monkey-patch the array's
47
- * push() method so new chunks are delivered immediately when the
48
- * server's <script> tags execute. This eliminates unnecessary
49
- * wakeups and reduces latency — same pattern Next.js uses with
50
- * __next_f. The stream closes on DOMContentLoaded (when all
51
- * server-injected scripts have executed) or when __VINEXT_RSC_DONE__
52
- * is set, whichever comes first.
53
- */
54
- function createProgressiveRscStream() {
55
- const encoder = new TextEncoder();
56
- return new ReadableStream({
57
- start(controller) {
58
- const chunks = self.__VINEXT_RSC_CHUNKS__ || [];
59
-
60
- // Deliver any chunks that arrived before this code ran
61
- // (from <script> tags that executed before the browser entry loaded)
62
- for (const chunk of chunks) {
63
- controller.enqueue(encoder.encode(chunk));
64
- }
65
-
66
- // If the stream is already complete, close immediately
67
- if (self.__VINEXT_RSC_DONE__) {
68
- controller.close();
69
- return;
70
- }
71
-
72
- // Monkey-patch push() so future chunks stream in immediately
73
- // when the server's <script> tags execute
74
- let closed = false;
75
- function closeOnce() {
76
- if (!closed) {
77
- closed = true;
78
- controller.close();
79
- }
80
- }
81
-
82
- const arr = self.__VINEXT_RSC_CHUNKS__ = self.__VINEXT_RSC_CHUNKS__ || [];
83
- arr.push = function(chunk) {
84
- Array.prototype.push.call(this, chunk);
85
- if (!closed) {
86
- controller.enqueue(encoder.encode(chunk));
87
- if (self.__VINEXT_RSC_DONE__) {
88
- closeOnce();
89
- }
90
- }
91
- return this.length;
92
- };
93
-
94
- // Safety net: if the server crashes mid-stream and __VINEXT_RSC_DONE__
95
- // never arrives, close the stream when all server-injected scripts
96
- // have executed (DOMContentLoaded). Without this, a truncated response
97
- // leaves the ReadableStream open forever, hanging hydration.
98
- if (typeof document !== "undefined") {
99
- if (document.readyState === "loading") {
100
- document.addEventListener("DOMContentLoaded", closeOnce);
101
- } else {
102
- // Document already loaded — close immediately if not already done
103
- closeOnce();
104
- }
105
- }
106
- }
107
- });
108
- }
109
-
110
- // Register the server action callback — React calls this internally
111
- // when a "use server" function is invoked from client code.
112
- setServerCallback(async (id, args) => {
113
- const temporaryReferences = createTemporaryReferenceSet();
114
- const body = await encodeReply(args, { temporaryReferences });
115
-
116
- const fetchResponse = await fetch(toRscUrl(window.location.pathname + window.location.search), {
117
- method: "POST",
118
- headers: { "x-rsc-action": id },
119
- body,
120
- });
121
-
122
- // Check for redirect signal from server action that called redirect()
123
- const actionRedirect = fetchResponse.headers.get("x-action-redirect");
124
- if (actionRedirect) {
125
- // External URLs (different origin) need a hard redirect — client-side
126
- // RSC navigation only works for same-origin paths.
127
- try {
128
- const redirectUrl = new URL(actionRedirect, window.location.origin);
129
- if (redirectUrl.origin !== window.location.origin) {
130
- window.location.href = actionRedirect;
131
- return undefined;
132
- }
133
- } catch {
134
- // If URL parsing fails, fall through to client-side navigation
135
- }
136
-
137
- // Navigate to the redirect target using client-side navigation
138
- const redirectType = fetchResponse.headers.get("x-action-redirect-type") || "replace";
139
- if (redirectType === "push") {
140
- window.history.pushState(null, "", actionRedirect);
141
- } else {
142
- window.history.replaceState(null, "", actionRedirect);
143
- }
144
- // Trigger RSC navigation to the redirect target
145
- if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") {
146
- window.__VINEXT_RSC_NAVIGATE__(actionRedirect);
147
- }
148
- return undefined;
149
- }
150
-
151
- const result = await createFromFetch(Promise.resolve(fetchResponse), { temporaryReferences });
152
-
153
- // The RSC response for actions contains { root, returnValue }.
154
- // Re-render the page with the updated tree.
155
- if (result && typeof result === "object" && "root" in result) {
156
- reactRoot.render(result.root);
157
- // Return the action's return value to the caller
158
- if (result.returnValue) {
159
- if (!result.returnValue.ok) throw result.returnValue.data;
160
- return result.returnValue.data;
161
- }
162
- return undefined;
163
- }
164
-
165
- // Fallback: render the entire result as the tree
166
- reactRoot.render(result);
167
- return result;
168
- });
169
-
170
- async function main() {
171
- let rscStream;
172
-
173
- // Use embedded RSC data for initial hydration if available.
174
- // This ensures we use the SAME RSC payload that generated the HTML,
175
- // avoiding hydration mismatches (React error #418).
176
- //
177
- // The server embeds RSC chunks progressively as <script> tags that push
178
- // to self.__VINEXT_RSC_CHUNKS__. When complete, self.__VINEXT_RSC_DONE__
179
- // is set and self.__VINEXT_RSC_PARAMS__ contains route params.
180
- // For backwards compat, also check the legacy self.__VINEXT_RSC__ format.
181
- if (self.__VINEXT_RSC_CHUNKS__ || self.__VINEXT_RSC_DONE__ || self.__VINEXT_RSC__) {
182
- if (self.__VINEXT_RSC__) {
183
- // Legacy format: single object with all chunks
184
- const embedData = self.__VINEXT_RSC__;
185
- delete self.__VINEXT_RSC__;
186
- if (embedData.params) {
187
- setClientParams(embedData.params);
188
- }
189
- // Legacy format may include nav context for hydration snapshot consistency.
190
- if (embedData.nav) {
191
- setNavigationContext({ pathname: embedData.nav.pathname, searchParams: new URLSearchParams(embedData.nav.searchParams || {}), params: embedData.params || {} });
192
- }
193
- rscStream = chunksToReadableStream(embedData.rsc);
194
- } else {
195
- // Progressive format: chunks arrive incrementally via script tags.
196
- // Params are embedded in <head> so they're always available by this point.
197
- if (self.__VINEXT_RSC_PARAMS__) {
198
- setClientParams(self.__VINEXT_RSC_PARAMS__);
199
- }
200
- // Restore the server navigation context so useSyncExternalStore getServerSnapshot
201
- // matches what was rendered on the server, preventing hydration mismatches.
202
- if (self.__VINEXT_RSC_NAV__) {
203
- const __nav = self.__VINEXT_RSC_NAV__;
204
- setNavigationContext({ pathname: __nav.pathname, searchParams: new URLSearchParams(__nav.searchParams), params: self.__VINEXT_RSC_PARAMS__ || {} });
205
- }
206
- rscStream = createProgressiveRscStream();
207
- }
208
- } else {
209
- // Fallback: fetch fresh RSC (shouldn't happen on initial page load)
210
- const rscResponse = await fetch(toRscUrl(window.location.pathname + window.location.search));
211
-
212
- // Hydrate useParams() with route params from the server before React hydration
213
- const paramsHeader = rscResponse.headers.get("X-Vinext-Params");
214
- if (paramsHeader) {
215
- try { setClientParams(JSON.parse(paramsHeader)); } catch (_e) { /* ignore */ }
216
- }
217
- // Set nav context from current URL for hydration snapshot consistency.
218
- setNavigationContext({ pathname: window.location.pathname, searchParams: new URLSearchParams(window.location.search), params: self.__VINEXT_RSC_PARAMS__ || {} });
219
-
220
- rscStream = rscResponse.body;
221
- }
222
-
223
- const root = await createFromReadableStream(rscStream);
224
-
225
- // Hydrate the document
226
- // In development, suppress Vite's error overlay for errors caught by React error
227
- // boundaries. Without this, React re-throws caught errors to the global handler,
228
- // which triggers Vite's overlay even though the error was handled by an error.tsx.
229
- // In production, preserve React's default onCaughtError (console.error) so
230
- // boundary-caught errors remain visible to error monitoring.
231
- reactRoot = hydrateRoot(document, root, import.meta.env.DEV ? {
232
- onCaughtError: function() {},
233
- } : undefined);
234
-
235
- // Store for client-side navigation
236
- window.__VINEXT_RSC_ROOT__ = reactRoot;
237
-
238
- // Client-side navigation handler
239
- // Checks the prefetch cache (populated by <Link> IntersectionObserver and
240
- // router.prefetch()) before making a network request. This makes navigation
241
- // near-instant for prefetched routes.
242
- window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href, __redirectDepth) {
243
- if ((__redirectDepth || 0) > 10) {
244
- console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
245
- window.location.href = href;
246
- return;
247
- }
248
- try {
249
- const url = new URL(href, window.location.origin);
250
- const rscUrl = toRscUrl(url.pathname + url.search);
251
-
252
- // Check the in-memory prefetch cache first
253
- let navResponse;
254
- const prefetchCache = getPrefetchCache();
255
- const cached = prefetchCache.get(rscUrl);
256
- if (cached && (Date.now() - cached.timestamp) < PREFETCH_CACHE_TTL) {
257
- navResponse = cached.response;
258
- prefetchCache.delete(rscUrl); // Consume the cached entry (one-time use)
259
- getPrefetchedUrls().delete(rscUrl); // Allow re-prefetch when link is visible again
260
- } else if (cached) {
261
- prefetchCache.delete(rscUrl); // Expired, clean up
262
- getPrefetchedUrls().delete(rscUrl);
263
- }
264
-
265
- // Fallback to network fetch if not in cache
266
- if (!navResponse) {
267
- navResponse = await fetch(rscUrl, {
268
- headers: { Accept: "text/x-component" },
269
- credentials: "include",
270
- });
271
- }
272
-
273
- // Detect if fetch followed a redirect: compare the final response URL to
274
- // what we requested. If they differ, the server issued a 3xx — push the
275
- // canonical destination URL into history before rendering.
276
- const __finalUrl = new URL(navResponse.url);
277
- const __requestedUrl = new URL(rscUrl, window.location.origin);
278
- if (__finalUrl.pathname !== __requestedUrl.pathname) {
279
- // Strip .rsc suffix from the final URL to get the page path for history.
280
- // Use replaceState instead of pushState: the caller (navigateImpl) already
281
- // pushed the pre-redirect URL; replacing it avoids a stale history entry.
282
- const __destPath = __finalUrl.pathname.replace(/\\.rsc$/, "") + __finalUrl.search;
283
- window.history.replaceState(null, "", __destPath);
284
- return window.__VINEXT_RSC_NAVIGATE__(__destPath, (__redirectDepth || 0) + 1);
285
- }
286
-
287
- // Update useParams() with route params from the server before re-rendering
288
- const navParamsHeader = navResponse.headers.get("X-Vinext-Params");
289
- if (navParamsHeader) {
290
- try { setClientParams(JSON.parse(navParamsHeader)); } catch (_e) { /* ignore */ }
291
- } else {
292
- setClientParams({});
293
- }
294
-
295
- const rscPayload = await createFromFetch(Promise.resolve(navResponse));
296
- // Use flushSync to guarantee React commits the new tree to the DOM
297
- // synchronously before this function returns. Callers scroll to top
298
- // after awaiting, so the new content must be painted first.
299
- flushSync(function () { reactRoot.render(rscPayload); });
300
- } catch (err) {
301
- console.error("[vinext] RSC navigation error:", err);
302
- // Fallback to full page load
303
- window.location.href = href;
304
- }
305
- };
306
-
307
- // Handle popstate (browser back/forward)
308
- // Store the navigation promise on a well-known property so that
309
- // restoreScrollPosition (in navigation.ts) can await it before scrolling.
310
- // This prevents a flash where the old content is visible at the restored
311
- // scroll position before the new RSC payload has rendered.
312
- window.addEventListener("popstate", () => {
313
- const p = window.__VINEXT_RSC_NAVIGATE__(window.location.href);
314
- window.__VINEXT_RSC_PENDING__ = p;
315
- p.finally(() => {
316
- // Clear once settled so stale promises aren't awaited later
317
- if (window.__VINEXT_RSC_PENDING__ === p) {
318
- window.__VINEXT_RSC_PENDING__ = null;
319
- }
320
- });
321
- });
322
-
323
- // HMR: re-render on server module updates
324
- if (import.meta.hot) {
325
- import.meta.hot.on("rsc:update", async () => {
326
- try {
327
- const rscPayload = await createFromFetch(
328
- fetch(toRscUrl(window.location.pathname + window.location.search))
329
- );
330
- reactRoot.render(rscPayload);
331
- } catch (err) {
332
- console.error("[vinext] RSC HMR error:", err);
333
- }
334
- });
335
- }
336
- }
337
-
338
- main();
339
- `;
11
+ const entryPath = resolveRuntimeEntryModule("app-browser-entry");
12
+ return `import ${JSON.stringify(entryPath)};`;
340
13
  }
341
14
  //#endregion
342
15
  export { generateBrowserEntry };
@@ -1 +1 @@
1
- {"version":3,"file":"app-browser-entry.js","names":[],"sources":["../../src/entries/app-browser-entry.ts"],"sourcesContent":["/**\n * Generate the virtual browser entry module.\n *\n * This runs in the client (browser). It hydrates the page from the\n * embedded RSC payload and handles client-side navigation by re-fetching\n * RSC streams.\n */\nexport function generateBrowserEntry(): string {\n return `\nimport {\n createFromReadableStream,\n createFromFetch,\n setServerCallback,\n encodeReply,\n createTemporaryReferenceSet,\n} from \"@vitejs/plugin-rsc/browser\";\nimport { hydrateRoot } from \"react-dom/client\";\nimport { flushSync } from \"react-dom\";\nimport { setClientParams, setNavigationContext, toRscUrl, getPrefetchCache, getPrefetchedUrls, PREFETCH_CACHE_TTL } from \"next/navigation\";\n\nlet reactRoot;\n\n/**\n * Convert the embedded RSC chunks back to a ReadableStream.\n * Each chunk is a text string that needs to be encoded back to Uint8Array.\n */\nfunction chunksToReadableStream(chunks) {\n const encoder = new TextEncoder();\n return new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(encoder.encode(chunk));\n }\n controller.close();\n }\n });\n}\n\n/**\n * Create a ReadableStream from progressively-embedded RSC chunks.\n * The server injects RSC data as <script> tags that push to\n * self.__VINEXT_RSC_CHUNKS__ throughout the HTML stream, and sets\n * self.__VINEXT_RSC_DONE__ = true when complete.\n *\n * Instead of polling with setTimeout, we monkey-patch the array's\n * push() method so new chunks are delivered immediately when the\n * server's <script> tags execute. This eliminates unnecessary\n * wakeups and reduces latency — same pattern Next.js uses with\n * __next_f. The stream closes on DOMContentLoaded (when all\n * server-injected scripts have executed) or when __VINEXT_RSC_DONE__\n * is set, whichever comes first.\n */\nfunction createProgressiveRscStream() {\n const encoder = new TextEncoder();\n return new ReadableStream({\n start(controller) {\n const chunks = self.__VINEXT_RSC_CHUNKS__ || [];\n\n // Deliver any chunks that arrived before this code ran\n // (from <script> tags that executed before the browser entry loaded)\n for (const chunk of chunks) {\n controller.enqueue(encoder.encode(chunk));\n }\n\n // If the stream is already complete, close immediately\n if (self.__VINEXT_RSC_DONE__) {\n controller.close();\n return;\n }\n\n // Monkey-patch push() so future chunks stream in immediately\n // when the server's <script> tags execute\n let closed = false;\n function closeOnce() {\n if (!closed) {\n closed = true;\n controller.close();\n }\n }\n\n const arr = self.__VINEXT_RSC_CHUNKS__ = self.__VINEXT_RSC_CHUNKS__ || [];\n arr.push = function(chunk) {\n Array.prototype.push.call(this, chunk);\n if (!closed) {\n controller.enqueue(encoder.encode(chunk));\n if (self.__VINEXT_RSC_DONE__) {\n closeOnce();\n }\n }\n return this.length;\n };\n\n // Safety net: if the server crashes mid-stream and __VINEXT_RSC_DONE__\n // never arrives, close the stream when all server-injected scripts\n // have executed (DOMContentLoaded). Without this, a truncated response\n // leaves the ReadableStream open forever, hanging hydration.\n if (typeof document !== \"undefined\") {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", closeOnce);\n } else {\n // Document already loaded — close immediately if not already done\n closeOnce();\n }\n }\n }\n });\n}\n\n// Register the server action callback — React calls this internally\n// when a \"use server\" function is invoked from client code.\nsetServerCallback(async (id, args) => {\n const temporaryReferences = createTemporaryReferenceSet();\n const body = await encodeReply(args, { temporaryReferences });\n\n const fetchResponse = await fetch(toRscUrl(window.location.pathname + window.location.search), {\n method: \"POST\",\n headers: { \"x-rsc-action\": id },\n body,\n });\n\n // Check for redirect signal from server action that called redirect()\n const actionRedirect = fetchResponse.headers.get(\"x-action-redirect\");\n if (actionRedirect) {\n // External URLs (different origin) need a hard redirect — client-side\n // RSC navigation only works for same-origin paths.\n try {\n const redirectUrl = new URL(actionRedirect, window.location.origin);\n if (redirectUrl.origin !== window.location.origin) {\n window.location.href = actionRedirect;\n return undefined;\n }\n } catch {\n // If URL parsing fails, fall through to client-side navigation\n }\n\n // Navigate to the redirect target using client-side navigation\n const redirectType = fetchResponse.headers.get(\"x-action-redirect-type\") || \"replace\";\n if (redirectType === \"push\") {\n window.history.pushState(null, \"\", actionRedirect);\n } else {\n window.history.replaceState(null, \"\", actionRedirect);\n }\n // Trigger RSC navigation to the redirect target\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n window.__VINEXT_RSC_NAVIGATE__(actionRedirect);\n }\n return undefined;\n }\n\n const result = await createFromFetch(Promise.resolve(fetchResponse), { temporaryReferences });\n\n // The RSC response for actions contains { root, returnValue }.\n // Re-render the page with the updated tree.\n if (result && typeof result === \"object\" && \"root\" in result) {\n reactRoot.render(result.root);\n // Return the action's return value to the caller\n if (result.returnValue) {\n if (!result.returnValue.ok) throw result.returnValue.data;\n return result.returnValue.data;\n }\n return undefined;\n }\n\n // Fallback: render the entire result as the tree\n reactRoot.render(result);\n return result;\n});\n\nasync function main() {\n let rscStream;\n\n // Use embedded RSC data for initial hydration if available.\n // This ensures we use the SAME RSC payload that generated the HTML,\n // avoiding hydration mismatches (React error #418).\n //\n // The server embeds RSC chunks progressively as <script> tags that push\n // to self.__VINEXT_RSC_CHUNKS__. When complete, self.__VINEXT_RSC_DONE__\n // is set and self.__VINEXT_RSC_PARAMS__ contains route params.\n // For backwards compat, also check the legacy self.__VINEXT_RSC__ format.\n if (self.__VINEXT_RSC_CHUNKS__ || self.__VINEXT_RSC_DONE__ || self.__VINEXT_RSC__) {\n if (self.__VINEXT_RSC__) {\n // Legacy format: single object with all chunks\n const embedData = self.__VINEXT_RSC__;\n delete self.__VINEXT_RSC__;\n if (embedData.params) {\n setClientParams(embedData.params);\n }\n // Legacy format may include nav context for hydration snapshot consistency.\n if (embedData.nav) {\n setNavigationContext({ pathname: embedData.nav.pathname, searchParams: new URLSearchParams(embedData.nav.searchParams || {}), params: embedData.params || {} });\n }\n rscStream = chunksToReadableStream(embedData.rsc);\n } else {\n // Progressive format: chunks arrive incrementally via script tags.\n // Params are embedded in <head> so they're always available by this point.\n if (self.__VINEXT_RSC_PARAMS__) {\n setClientParams(self.__VINEXT_RSC_PARAMS__);\n }\n // Restore the server navigation context so useSyncExternalStore getServerSnapshot\n // matches what was rendered on the server, preventing hydration mismatches.\n if (self.__VINEXT_RSC_NAV__) {\n const __nav = self.__VINEXT_RSC_NAV__;\n setNavigationContext({ pathname: __nav.pathname, searchParams: new URLSearchParams(__nav.searchParams), params: self.__VINEXT_RSC_PARAMS__ || {} });\n }\n rscStream = createProgressiveRscStream();\n }\n } else {\n // Fallback: fetch fresh RSC (shouldn't happen on initial page load)\n const rscResponse = await fetch(toRscUrl(window.location.pathname + window.location.search));\n\n // Hydrate useParams() with route params from the server before React hydration\n const paramsHeader = rscResponse.headers.get(\"X-Vinext-Params\");\n if (paramsHeader) {\n try { setClientParams(JSON.parse(paramsHeader)); } catch (_e) { /* ignore */ }\n }\n // Set nav context from current URL for hydration snapshot consistency.\n setNavigationContext({ pathname: window.location.pathname, searchParams: new URLSearchParams(window.location.search), params: self.__VINEXT_RSC_PARAMS__ || {} });\n\n rscStream = rscResponse.body;\n }\n\n const root = await createFromReadableStream(rscStream);\n\n // Hydrate the document\n // In development, suppress Vite's error overlay for errors caught by React error\n // boundaries. Without this, React re-throws caught errors to the global handler,\n // which triggers Vite's overlay even though the error was handled by an error.tsx.\n // In production, preserve React's default onCaughtError (console.error) so\n // boundary-caught errors remain visible to error monitoring.\n reactRoot = hydrateRoot(document, root, import.meta.env.DEV ? {\n onCaughtError: function() {},\n } : undefined);\n\n // Store for client-side navigation\n window.__VINEXT_RSC_ROOT__ = reactRoot;\n\n // Client-side navigation handler\n // Checks the prefetch cache (populated by <Link> IntersectionObserver and\n // router.prefetch()) before making a network request. This makes navigation\n // near-instant for prefetched routes.\n window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href, __redirectDepth) {\n if ((__redirectDepth || 0) > 10) {\n console.error(\"[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.\");\n window.location.href = href;\n return;\n }\n try {\n const url = new URL(href, window.location.origin);\n const rscUrl = toRscUrl(url.pathname + url.search);\n\n // Check the in-memory prefetch cache first\n let navResponse;\n const prefetchCache = getPrefetchCache();\n const cached = prefetchCache.get(rscUrl);\n if (cached && (Date.now() - cached.timestamp) < PREFETCH_CACHE_TTL) {\n navResponse = cached.response;\n prefetchCache.delete(rscUrl); // Consume the cached entry (one-time use)\n getPrefetchedUrls().delete(rscUrl); // Allow re-prefetch when link is visible again\n } else if (cached) {\n prefetchCache.delete(rscUrl); // Expired, clean up\n getPrefetchedUrls().delete(rscUrl);\n }\n\n // Fallback to network fetch if not in cache\n if (!navResponse) {\n navResponse = await fetch(rscUrl, {\n headers: { Accept: \"text/x-component\" },\n credentials: \"include\",\n });\n }\n\n // Detect if fetch followed a redirect: compare the final response URL to\n // what we requested. If they differ, the server issued a 3xx — push the\n // canonical destination URL into history before rendering.\n const __finalUrl = new URL(navResponse.url);\n const __requestedUrl = new URL(rscUrl, window.location.origin);\n if (__finalUrl.pathname !== __requestedUrl.pathname) {\n // Strip .rsc suffix from the final URL to get the page path for history.\n // Use replaceState instead of pushState: the caller (navigateImpl) already\n // pushed the pre-redirect URL; replacing it avoids a stale history entry.\n const __destPath = __finalUrl.pathname.replace(/\\\\.rsc$/, \"\") + __finalUrl.search;\n window.history.replaceState(null, \"\", __destPath);\n return window.__VINEXT_RSC_NAVIGATE__(__destPath, (__redirectDepth || 0) + 1);\n }\n\n // Update useParams() with route params from the server before re-rendering\n const navParamsHeader = navResponse.headers.get(\"X-Vinext-Params\");\n if (navParamsHeader) {\n try { setClientParams(JSON.parse(navParamsHeader)); } catch (_e) { /* ignore */ }\n } else {\n setClientParams({});\n }\n\n const rscPayload = await createFromFetch(Promise.resolve(navResponse));\n // Use flushSync to guarantee React commits the new tree to the DOM\n // synchronously before this function returns. Callers scroll to top\n // after awaiting, so the new content must be painted first.\n flushSync(function () { reactRoot.render(rscPayload); });\n } catch (err) {\n console.error(\"[vinext] RSC navigation error:\", err);\n // Fallback to full page load\n window.location.href = href;\n }\n };\n\n // Handle popstate (browser back/forward)\n // Store the navigation promise on a well-known property so that\n // restoreScrollPosition (in navigation.ts) can await it before scrolling.\n // This prevents a flash where the old content is visible at the restored\n // scroll position before the new RSC payload has rendered.\n window.addEventListener(\"popstate\", () => {\n const p = window.__VINEXT_RSC_NAVIGATE__(window.location.href);\n window.__VINEXT_RSC_PENDING__ = p;\n p.finally(() => {\n // Clear once settled so stale promises aren't awaited later\n if (window.__VINEXT_RSC_PENDING__ === p) {\n window.__VINEXT_RSC_PENDING__ = null;\n }\n });\n });\n\n // HMR: re-render on server module updates\n if (import.meta.hot) {\n import.meta.hot.on(\"rsc:update\", async () => {\n try {\n const rscPayload = await createFromFetch(\n fetch(toRscUrl(window.location.pathname + window.location.search))\n );\n reactRoot.render(rscPayload);\n } catch (err) {\n console.error(\"[vinext] RSC HMR error:\", err);\n }\n });\n }\n}\n\nmain();\n`;\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,uBAA+B;AAC7C,QAAO"}
1
+ {"version":3,"file":"app-browser-entry.js","names":[],"sources":["../../src/entries/app-browser-entry.ts"],"sourcesContent":["import { resolveRuntimeEntryModule } from \"./runtime-entry-module.js\";\n\n/**\n * Generate the virtual browser entry module.\n *\n * This runs in the client (browser). It hydrates the page from the\n * embedded RSC payload and handles client-side navigation by re-fetching\n * RSC streams.\n */\nexport function generateBrowserEntry(): string {\n const entryPath = resolveRuntimeEntryModule(\"app-browser-entry\");\n return `import ${JSON.stringify(entryPath)};`;\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,uBAA+B;CAC7C,MAAM,YAAY,0BAA0B,oBAAoB;AAChE,QAAO,UAAU,KAAK,UAAU,UAAU,CAAC"}