@kasperpeulen/vitest-plugin-nextjs 0.0.0-alpha.0 → 0.1.0-alpha.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 (250) hide show
  1. package/README.md +249 -2
  2. package/dist/_virtual/_rolldown/runtime.js +14 -0
  3. package/dist/action-ids.d.ts +18 -0
  4. package/dist/action-ids.js +44 -0
  5. package/dist/adapter.d.ts +8 -0
  6. package/dist/adapter.js +12 -0
  7. package/dist/app-requests.d.ts +63 -0
  8. package/dist/app-requests.js +253 -0
  9. package/dist/browser-context.d.ts +24 -0
  10. package/dist/browser-context.js +157 -0
  11. package/dist/build-output.d.ts +68 -0
  12. package/dist/build-output.js +216 -0
  13. package/dist/build.d.ts +26 -0
  14. package/dist/build.js +39 -0
  15. package/dist/built-files.d.ts +9 -0
  16. package/dist/built-files.js +22 -0
  17. package/dist/cache.d.ts +44 -0
  18. package/dist/cache.js +99 -0
  19. package/dist/client-components.d.ts +3 -0
  20. package/dist/client-components.js +4 -0
  21. package/dist/client-file-key.d.ts +5 -0
  22. package/dist/client-file-key.js +5 -0
  23. package/dist/client-files.d.ts +83 -0
  24. package/dist/client-files.js +186 -0
  25. package/dist/client-graph.d.ts +94 -0
  26. package/dist/client-graph.js +266 -0
  27. package/dist/client-modules.d.ts +10 -0
  28. package/dist/client-modules.js +43 -0
  29. package/dist/client.d.ts +42 -0
  30. package/dist/client.js +263 -0
  31. package/dist/commonjs.d.ts +44 -0
  32. package/dist/commonjs.js +111 -0
  33. package/dist/compat/browser/client.d.ts +19 -0
  34. package/dist/compat/browser/client.js +13 -0
  35. package/dist/compat/build.d.ts +145 -0
  36. package/dist/compat/build.js +149 -0
  37. package/dist/compat/checks.d.ts +16 -0
  38. package/dist/compat/checks.js +148 -0
  39. package/dist/compat/patches.d.ts +13 -0
  40. package/dist/compat/patches.js +30 -0
  41. package/dist/compat/paths.d.ts +74 -0
  42. package/dist/compat/paths.js +160 -0
  43. package/dist/compat/rsc/handlers.d.ts +2 -0
  44. package/dist/compat/rsc/handlers.js +2 -0
  45. package/dist/compat/rsc/prerender.d.ts +10 -0
  46. package/dist/compat/rsc/prerender.js +9 -0
  47. package/dist/compat/server/headers.d.ts +3 -0
  48. package/dist/compat/server/headers.js +3 -0
  49. package/dist/compat/ssr/build-output.d.ts +4 -0
  50. package/dist/compat/ssr/build-output.js +3 -0
  51. package/dist/compat/ssr/cache.d.ts +6 -0
  52. package/dist/compat/ssr/cache.js +6 -0
  53. package/dist/compat/ssr/handler.d.ts +8 -0
  54. package/dist/compat/ssr/handler.js +8 -0
  55. package/dist/compat/ssr/node-server.d.ts +5 -0
  56. package/dist/compat/ssr/node-server.js +4 -0
  57. package/dist/compat/ssr/render-errors.d.ts +4 -0
  58. package/dist/compat/ssr/render-errors.js +4 -0
  59. package/dist/compat/ssr/senders.d.ts +6 -0
  60. package/dist/compat/ssr/senders.js +6 -0
  61. package/dist/compat/types.d.ts +8 -0
  62. package/dist/compat/types.js +1 -0
  63. package/dist/compile.d.ts +31 -0
  64. package/dist/compile.js +140 -0
  65. package/dist/disk.d.ts +16 -0
  66. package/dist/disk.js +13 -0
  67. package/dist/document-loads.d.ts +15 -0
  68. package/dist/document-loads.js +53 -0
  69. package/dist/document.d.ts +79 -0
  70. package/dist/document.js +328 -0
  71. package/dist/downloads.d.ts +15 -0
  72. package/dist/downloads.js +71 -0
  73. package/dist/esm-require.d.ts +11 -0
  74. package/dist/esm-require.js +32 -0
  75. package/dist/fast-crypto/index.js +1093 -0
  76. package/dist/fast-crypto.d.ts +20 -0
  77. package/dist/fast-crypto.js +151 -0
  78. package/dist/flight.d.ts +19 -0
  79. package/dist/flight.js +42 -0
  80. package/dist/form-submission.d.ts +15 -0
  81. package/dist/form-submission.js +27 -0
  82. package/dist/from-page.d.ts +5 -0
  83. package/dist/from-page.js +44 -0
  84. package/dist/globals.d.ts +1 -0
  85. package/dist/globals.js +197 -0
  86. package/dist/host-document.d.ts +26 -0
  87. package/dist/host-document.js +34 -0
  88. package/dist/index.d.ts +280 -0
  89. package/dist/index.js +281 -0
  90. package/dist/internal.d.ts +2 -0
  91. package/dist/internal.js +3 -0
  92. package/dist/leftovers.d.ts +11 -0
  93. package/dist/leftovers.js +55 -0
  94. package/dist/mode.d.ts +169 -0
  95. package/dist/mode.js +31 -0
  96. package/dist/node/LICENSES.md +3540 -0
  97. package/dist/node/assert/strict.js +29 -0
  98. package/dist/node/assert.js +3044 -0
  99. package/dist/node/buffer.js +1499 -0
  100. package/dist/node/child_process.js +45 -0
  101. package/dist/node/cluster.js +85 -0
  102. package/dist/node/console.js +96 -0
  103. package/dist/node/constants.js +495 -0
  104. package/dist/node/crypto.js +1388 -0
  105. package/dist/node/dgram.js +78 -0
  106. package/dist/node/diagnostics_channel.js +122 -0
  107. package/dist/node/dns/promises.js +129 -0
  108. package/dist/node/dns.js +137 -0
  109. package/dist/node/domain.js +43 -0
  110. package/dist/node/events.js +1155 -0
  111. package/dist/node/fs/promises.js +40 -0
  112. package/dist/node/fs.js +10375 -0
  113. package/dist/node/http.js +326 -0
  114. package/dist/node/http2.js +305 -0
  115. package/dist/node/https.js +57 -0
  116. package/dist/node/inspector/promises.js +66 -0
  117. package/dist/node/inspector.js +76 -0
  118. package/dist/node/module.js +205 -0
  119. package/dist/node/net.js +392 -0
  120. package/dist/node/os.js +259 -0
  121. package/dist/node/path/posix.js +25 -0
  122. package/dist/node/path/win32.js +25 -0
  123. package/dist/node/path.js +384 -0
  124. package/dist/node/perf_hooks.js +313 -0
  125. package/dist/node/process.js +452 -0
  126. package/dist/node/punycode.js +178 -0
  127. package/dist/node/querystring.js +934 -0
  128. package/dist/node/readline/promises.js +71 -0
  129. package/dist/node/readline.js +56 -0
  130. package/dist/node/repl.js +44 -0
  131. package/dist/node/sqlite.js +22 -0
  132. package/dist/node/stream/consumers.js +47 -0
  133. package/dist/node/stream/promises.js +6 -0
  134. package/dist/node/stream/web.js +52 -0
  135. package/dist/node/stream.js +4323 -0
  136. package/dist/node/string_decoder.js +276 -0
  137. package/dist/node/sys.js +6 -0
  138. package/dist/node/timers/promises.js +71 -0
  139. package/dist/node/timers.js +133 -0
  140. package/dist/node/tls.js +122 -0
  141. package/dist/node/trace_events.js +23 -0
  142. package/dist/node/tty.js +69 -0
  143. package/dist/node/url.js +1426 -0
  144. package/dist/node/util/types.js +1456 -0
  145. package/dist/node/util.js +4807 -0
  146. package/dist/node/v8.js +160 -0
  147. package/dist/node/vm.js +88 -0
  148. package/dist/node/wasi.js +16 -0
  149. package/dist/node/worker_threads.js +123 -0
  150. package/dist/node/zlib.js +1396 -0
  151. package/dist/node-builtins.d.ts +40 -0
  152. package/dist/node-builtins.js +74 -0
  153. package/dist/node-on-page.d.ts +46 -0
  154. package/dist/node-on-page.js +101 -0
  155. package/dist/node-platform.d.ts +36 -0
  156. package/dist/node-platform.js +152 -0
  157. package/dist/node-rendered.d.ts +21 -0
  158. package/dist/node-rendered.js +26 -0
  159. package/dist/node-server.d.ts +46 -0
  160. package/dist/node-server.js +193 -0
  161. package/dist/optimizer-cache-key.d.ts +26 -0
  162. package/dist/optimizer-cache-key.js +73 -0
  163. package/dist/own-package.d.ts +1 -0
  164. package/dist/own-package.js +6 -0
  165. package/dist/page-store.d.ts +33 -0
  166. package/dist/page-store.js +80 -0
  167. package/dist/page-timers.d.ts +22 -0
  168. package/dist/page-timers.js +47 -0
  169. package/dist/page.d.ts +112 -0
  170. package/dist/page.js +549 -0
  171. package/dist/paths.d.ts +13 -0
  172. package/dist/paths.js +47 -0
  173. package/dist/plugin.d.ts +22 -0
  174. package/dist/plugin.js +995 -0
  175. package/dist/prerender-verdict.d.ts +8 -0
  176. package/dist/prerender-verdict.js +67 -0
  177. package/dist/project/context.d.ts +546 -0
  178. package/dist/project/context.js +111 -0
  179. package/dist/project/entries.d.ts +42 -0
  180. package/dist/project/entries.js +189 -0
  181. package/dist/project/env-worker.d.ts +1 -0
  182. package/dist/project/env-worker.js +9 -0
  183. package/dist/project/env.d.ts +36 -0
  184. package/dist/project/env.js +82 -0
  185. package/dist/project/layers.d.ts +31 -0
  186. package/dist/project/layers.js +150 -0
  187. package/dist/project/loaders.d.ts +71 -0
  188. package/dist/project/loaders.js +300 -0
  189. package/dist/project/metadata.d.ts +8 -0
  190. package/dist/project/metadata.js +34 -0
  191. package/dist/project/module-exports.d.ts +29 -0
  192. package/dist/project/module-exports.js +77 -0
  193. package/dist/project/routes.d.ts +19 -0
  194. package/dist/project/routes.js +92 -0
  195. package/dist/project/routing.d.ts +46 -0
  196. package/dist/project/routing.js +280 -0
  197. package/dist/project/transform.d.ts +8 -0
  198. package/dist/project/transform.js +110 -0
  199. package/dist/project/webpack.d.ts +46 -0
  200. package/dist/project/webpack.js +160 -0
  201. package/dist/project.d.ts +264 -0
  202. package/dist/project.js +58 -0
  203. package/dist/registry-global.d.ts +5 -0
  204. package/dist/registry-global.js +5 -0
  205. package/dist/registry.d.ts +241 -0
  206. package/dist/registry.js +30 -0
  207. package/dist/route-chunks.d.ts +13 -0
  208. package/dist/route-chunks.js +32 -0
  209. package/dist/route-lookup.d.ts +34 -0
  210. package/dist/route-lookup.js +207 -0
  211. package/dist/routes-ahead-shared.d.ts +17 -0
  212. package/dist/routes-ahead-shared.js +9 -0
  213. package/dist/routes-ahead.d.ts +10 -0
  214. package/dist/routes-ahead.js +54 -0
  215. package/dist/rsc.d.ts +19 -0
  216. package/dist/rsc.js +171 -0
  217. package/dist/server-action.d.ts +9 -0
  218. package/dist/server-action.js +15 -0
  219. package/dist/server-code.d.ts +176 -0
  220. package/dist/server-code.js +357 -0
  221. package/dist/server-in-front.d.ts +37 -0
  222. package/dist/server-in-front.js +90 -0
  223. package/dist/server-layers.d.ts +22 -0
  224. package/dist/server-layers.js +33 -0
  225. package/dist/setup-browser.d.ts +1 -0
  226. package/dist/setup-browser.js +8 -0
  227. package/dist/setup-node.d.ts +1 -0
  228. package/dist/setup-node.js +6 -0
  229. package/dist/setup.d.ts +8 -0
  230. package/dist/setup.js +62 -0
  231. package/dist/speed-features.d.ts +13 -0
  232. package/dist/speed-features.js +95 -0
  233. package/dist/ssr.d.ts +70 -0
  234. package/dist/ssr.js +480 -0
  235. package/dist/static-build.d.ts +36 -0
  236. package/dist/static-build.js +92 -0
  237. package/dist/storage.d.ts +31 -0
  238. package/dist/storage.js +94 -0
  239. package/dist/styles-shared.d.ts +137 -0
  240. package/dist/styles-shared.js +124 -0
  241. package/dist/styles.d.ts +102 -0
  242. package/dist/styles.js +730 -0
  243. package/dist/stylesheet-versions.d.ts +17 -0
  244. package/dist/stylesheet-versions.js +43 -0
  245. package/dist/tester.html +10 -0
  246. package/dist/use-cache.d.ts +15 -0
  247. package/dist/use-cache.js +93 -0
  248. package/dist/vitest-version.d.ts +6 -0
  249. package/dist/vitest-version.js +12 -0
  250. package/package.json +125 -4
package/dist/ssr.js ADDED
@@ -0,0 +1,480 @@
1
+ import { openedAt, registry } from "./registry.js";
2
+ import { actionIdsOf } from "./action-ids.js";
3
+ import { incoming, middlewareResult, redirectStatus, resolve } from "./server-in-front.js";
4
+ import { demoteRoute, isPrerendered, nodeRouteDistDir, prerenderManifest, recordRoute, setServerActions, setStylesheets, withoutRoute } from "./build-output.js";
5
+ import { loadCacheHandlers, resetCaches, shareIncrementalCache } from "./cache.js";
6
+ import { registerModuleLoader } from "./client-modules.js";
7
+ import { countBailouts, handleRequest as handleRequest$1, isPrerenderBailout, writingOf } from "./node-server.js";
8
+ import { NEXT_REWRITTEN_PATH_HEADER, NEXT_REWRITTEN_QUERY_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_RSC_UNION_QUERY, RSC_HEADER, appPageModule, getIsPossibleServerAction, getRouteMatcher, getRouteRegex, isRSCRequestHeader, normalizeNextQueryParam } from "./compat/ssr/handler.js";
9
+ import { getAccessFallbackHTTPStatus, getRedirectStatusCodeFromError, getURLFromRedirectError, isHTTPAccessFallbackError, isRedirectError } from "./compat/ssr/render-errors.js";
10
+ import { build, routes, routing } from "virtual:vitest-plugin-rsc/next-manifest";
11
+ //#region src/ssr.ts
12
+ registry.ssr = appPageModule;
13
+ registerModuleLoader("ssr");
14
+ const routes$1 = new Map(routes.filter((route) => !route.component).map((route) => [route.page, route]));
15
+ const componentRoutes = new Map(routes.filter((route) => route.component && !route.layouts).map((route) => [route.pathname, route]));
16
+ const componentLayoutRoutes = new Map(routes.filter((route) => route.layouts).map((route) => [route.page, route]));
17
+ const notFoundPage = "/_not-found/page";
18
+ function appQuery(query) {
19
+ const result = Object.create(null);
20
+ for (const [name, value] of query) {
21
+ if (normalizeNextQueryParam(name) !== null) continue;
22
+ result[name] = name in result ? [result[name], value].flat() : value;
23
+ }
24
+ return result;
25
+ }
26
+ function searchOf(query) {
27
+ const search = new URLSearchParams();
28
+ for (const [name, value] of Object.entries(query)) {
29
+ if (name === NEXT_RSC_UNION_QUERY) continue;
30
+ for (const item of [value].flat()) search.append(name, item);
31
+ }
32
+ return search.toString();
33
+ }
34
+ const paramMatchers = /* @__PURE__ */ new Map();
35
+ function paramsOf(route, pathname) {
36
+ let match = paramMatchers.get(route);
37
+ if (!match) {
38
+ const { re, groups } = getRouteRegex(route);
39
+ match = getRouteMatcher({
40
+ re: new RegExp(re.source, "i"),
41
+ groups
42
+ });
43
+ paramMatchers.set(route, match);
44
+ }
45
+ try {
46
+ return match(pathname) || void 0;
47
+ } catch {
48
+ return;
49
+ }
50
+ }
51
+ function withoutBasePath(pathname) {
52
+ const { basePath } = routing;
53
+ return basePath && pathname.startsWith(basePath) ? pathname.slice(basePath.length) || "/" : pathname;
54
+ }
55
+ const setTimeout = globalThis.setTimeout;
56
+ const clearTimeout = globalThis.clearTimeout;
57
+ const now = performance.now.bind(performance);
58
+ let queue = Promise.resolve();
59
+ let queued = 0;
60
+ let requestQueued = Promise.withResolvers();
61
+ const nextRequest = () => queued > 0 ? Promise.resolve() : requestQueued.promise;
62
+ const rendering = /* @__PURE__ */ new Set();
63
+ let generation = 0;
64
+ const backgroundWorkTimeout = 1e3;
65
+ const backgroundWork = /* @__PURE__ */ new Set();
66
+ const unanswered = /* @__PURE__ */ new Map();
67
+ const settleTimeout = 5e3;
68
+ function streamOf(body) {
69
+ return new ReadableStream({ start(controller) {
70
+ if (body) controller.enqueue(body);
71
+ controller.close();
72
+ } });
73
+ }
74
+ function handleRequest(request, { nested = false, unrouted = "not-found", opened, pageLoad = false } = {}) {
75
+ if (nested) return untilAnswered(request, handle(request, unrouted, true, opened, pageLoad)).then(({ response }) => response);
76
+ const requested = generation;
77
+ queued++;
78
+ requestQueued.resolve();
79
+ const timing = registry.requestTimings && timingOf(request);
80
+ if (timing) registry.requestTimings.push(timing);
81
+ const result = queue.then(() => {
82
+ if (requested !== generation) throw new DOMException("The page was left before the server responded.", "AbortError");
83
+ queued--;
84
+ requestQueued = Promise.withResolvers();
85
+ if (timing) timing.started = performance.now();
86
+ const handled = untilAnswered(request, handle(request, unrouted, false, opened, pageLoad));
87
+ if (timing) handled.then(({ finished }) => {
88
+ timing.answered = performance.now();
89
+ const record = () => timing.finished = performance.now();
90
+ finished.then(record, record);
91
+ }, () => {});
92
+ return handled;
93
+ });
94
+ queue = result.then(({ finished }) => finished, () => {});
95
+ return result.then(({ response }) => response);
96
+ }
97
+ function timingOf(request) {
98
+ const { headers } = request;
99
+ const flight = headers.has("rsc") && (headers.has("next-router-state-tree") || headers.has("next-router-prefetch"));
100
+ const kind = headers.has("next-action") ? "action" : flight ? "flight" : "page";
101
+ return {
102
+ method: request.method,
103
+ url: request.url,
104
+ kind,
105
+ queued: performance.now()
106
+ };
107
+ }
108
+ function untilAnswered(request, handled) {
109
+ const settled = handled.then(() => {}, () => {});
110
+ unanswered.set(settled, `${request.method} ${new URL(request.url).pathname}`);
111
+ settled.then(() => unanswered.delete(settled));
112
+ return handled;
113
+ }
114
+ async function stopResponses(written) {
115
+ await Promise.race([written, new Promise((resolve) => setTimeout(resolve, 100))]);
116
+ for (const stop of rendering) stop();
117
+ rendering.clear();
118
+ }
119
+ async function untilDone(work, deadline) {
120
+ let done = true;
121
+ for (let waiting = [...work()]; waiting.length > 0 && done; waiting = [...work()]) {
122
+ let timeout;
123
+ done = await Promise.race([Promise.all(waiting).then(() => true), new Promise((resolve) => timeout = setTimeout(resolve, Math.max(0, deadline - now()), false))]);
124
+ clearTimeout(timeout);
125
+ }
126
+ return done;
127
+ }
128
+ /**
129
+ * How Next's server answers the document of a page when an error fails the
130
+ * render of its HTML before any of it is out, by the digest of the error, as
131
+ * Next's renderer does in the `catch` of `renderToStream()` (app-render.js):
132
+ * the status of a `notFound()`, `forbidden()` or `unauthorized()`, the 307 or
133
+ * 308 of a `redirect()` and where it goes, without the app's `basePath`, and
134
+ * 500 for any other error. For a page that the server renders no HTML of: see
135
+ * `renderFailureOf()` in page.ts.
136
+ */
137
+ function shellErrorAnswer(digest) {
138
+ const error = Object.assign(/* @__PURE__ */ new Error(), { digest });
139
+ if (isHTTPAccessFallbackError(error)) return { status: getAccessFallbackHTTPStatus(error) };
140
+ if (isRedirectError(error)) return {
141
+ status: getRedirectStatusCodeFromError(error),
142
+ location: getURLFromRedirectError(error)
143
+ };
144
+ return { status: 500 };
145
+ }
146
+ /**
147
+ * Ends the requests the server is still handling, when a page is left.
148
+ *
149
+ * A request that has its answer is writing the body: a short wait, then a
150
+ * stop. A test can end while a page streams, on data that will never come.
151
+ *
152
+ * A request that has no answer yet runs code of the app, like a Server Action
153
+ * or a route handler, and nothing can stop that. So it is waited for, and
154
+ * then for the body it writes. So is what requests do after their response,
155
+ * like `after()`. What comes next is not to run while any of that still reads
156
+ * or sets the stores of a request, which would be the ones of the requests
157
+ * that come next. A request that is sent in the meantime waits for all of
158
+ * that, and is dropped: it is one of the page that was left.
159
+ */
160
+ async function settleRequests() {
161
+ generation++;
162
+ const written = queue;
163
+ const settled = Promise.withResolvers();
164
+ queue = settled.promise;
165
+ queued = 0;
166
+ const waited = requestQueued;
167
+ requestQueued = Promise.withResolvers();
168
+ const deadline = now() + settleTimeout;
169
+ try {
170
+ await stopResponses(written);
171
+ if (unanswered.size > 0) {
172
+ if (!await untilDone(() => unanswered.keys(), deadline)) {
173
+ console.warn(`vitest-plugin-rsc: the server was still handling ${[...unanswered.values()].join(", ")} ${settleTimeout / 1e3}s after the page was left. What comes next runs anyway. Until that request is done, its code and the requests that come next can read and set each other's cookies, headers and other values of a request.`);
174
+ unanswered.clear();
175
+ }
176
+ await stopResponses(new Promise(() => {}));
177
+ }
178
+ const worked = await untilDone(() => backgroundWork, deadline);
179
+ waited.resolve();
180
+ if (!worked) {
181
+ backgroundWork.clear();
182
+ console.warn(`vitest-plugin-rsc: \`after()\` or \`waitUntil()\` work of a request was still running ${settleTimeout / 1e3}s after a page was left. What comes next runs anyway, and that work may read the stores of the requests that come next.`);
183
+ }
184
+ } finally {
185
+ generation++;
186
+ queued = 0;
187
+ waited.resolve();
188
+ requestQueued.resolve();
189
+ requestQueued = Promise.withResolvers();
190
+ settled.resolve();
191
+ }
192
+ }
193
+ function nodeRouteFor(opened, matched) {
194
+ const node = opened?.node;
195
+ if (!node) return void 0;
196
+ if (!node.layouts) return componentRoutes.get(matched?.pathname ?? "/") ?? componentRoutes.get("/");
197
+ const withLayouts = componentLayoutRoutes.get(matched?.page ?? "");
198
+ if (!withLayouts) throw new Error(`vitest-plugin-rsc: \`layouts: true\` renders a node in place of the \`page\` file of its \`url\`, and the app has none for ${opened.pathname}.`);
199
+ return withLayouts;
200
+ }
201
+ const verdicts = /* @__PURE__ */ new Map();
202
+ function verdictOf(route) {
203
+ let verdict = verdicts.get(route.page);
204
+ if (!verdict) {
205
+ const endScope = registry.enterRequestScope();
206
+ verdict = registry.prerenderVerdict(route).finally(endScope);
207
+ verdicts.set(route.page, verdict);
208
+ verdict.catch(() => verdicts.delete(route.page));
209
+ }
210
+ return verdict;
211
+ }
212
+ const recorded = /* @__PURE__ */ new Set();
213
+ async function recordVerdict(route) {
214
+ const verdict = await verdictOf(route);
215
+ if (recorded.has(route.page)) return;
216
+ recorded.add(route.page);
217
+ recordRoute(route, verdict);
218
+ }
219
+ async function isUnlisted(route, pathname) {
220
+ if (recorded.has(route.page)) return prerenderManifest.dynamicRoutes[route.pathname]?.fallback === false && !(pathname in prerenderManifest.routes);
221
+ const verdict = await verdictOf(route);
222
+ return verdict?.fallback === false && !verdict.pathnames.includes(pathname);
223
+ }
224
+ async function prerenderFirst(route, scope, respond) {
225
+ if (!route || !isPrerendered(route)) return respond();
226
+ const endPrerender = scope.enterInner();
227
+ const bailouts = countBailouts();
228
+ try {
229
+ const response = await respond();
230
+ const bailedOut = bailouts() > 0;
231
+ if (response.status !== 500 || !bailedOut) return response;
232
+ await response.body?.cancel();
233
+ } catch (error) {
234
+ bailouts();
235
+ if (!isPrerenderBailout(error)) throw error;
236
+ }
237
+ endPrerender();
238
+ demoteRoute(route);
239
+ return respond();
240
+ }
241
+ async function serverActionsOf(request) {
242
+ const own = await registry.ownServerAction;
243
+ const ids = new Set(own ? [own] : []);
244
+ for (const id of await actionIdsOf(request)) if (await registry.hasServerAction(id)) ids.add(id);
245
+ return ids;
246
+ }
247
+ function enterScope(waited) {
248
+ const endScope = registry.enterRequestScope();
249
+ const background = [];
250
+ const waitUntil = (promise) => {
251
+ background.push(promise);
252
+ const work = promise.then(() => {}, () => {});
253
+ backgroundWork.add(work);
254
+ work.then(() => backgroundWork.delete(work));
255
+ };
256
+ const backgroundSettled = async () => {
257
+ for (let settled = 0; settled < background.length;) {
258
+ const waiting = background.slice(settled);
259
+ settled = background.length;
260
+ await Promise.allSettled(waiting);
261
+ }
262
+ };
263
+ const inner = [];
264
+ const enterInner = () => {
265
+ const endInner = registry.enterRequestScope();
266
+ inner.push(endInner);
267
+ return endInner;
268
+ };
269
+ const endAll = () => {
270
+ for (const endInner of inner) endInner();
271
+ endScope();
272
+ };
273
+ const end = async () => {
274
+ await Promise.race([backgroundSettled(), waited().then(() => new Promise((resolve) => setTimeout(resolve, backgroundWorkTimeout)))]);
275
+ endAll();
276
+ };
277
+ return {
278
+ waitUntil,
279
+ end,
280
+ endScope: endAll,
281
+ enterInner
282
+ };
283
+ }
284
+ async function handle(received, unrouted, nested, openedByRequest, pageLoad) {
285
+ const request = incoming(received);
286
+ const url = new URL(request.url);
287
+ const opened = openedByRequest === null ? void 0 : openedByRequest ?? openedAt(url);
288
+ const scope = enterScope(nested ? () => Promise.resolve() : nextRequest);
289
+ registry.openedByRequest.run(opened, () => {});
290
+ const context = { waitUntil: scope.waitUntil };
291
+ const endRequest = scope.end;
292
+ try {
293
+ let { body } = request;
294
+ let middlewareBody;
295
+ if (body instanceof ReadableStream) [middlewareBody, body] = body.tee();
296
+ else middlewareBody = streamOf(body);
297
+ let responded;
298
+ let { headers } = request;
299
+ const invokeMiddleware = async (target) => {
300
+ const middlewareScope = enterScope(() => Promise.resolve());
301
+ let response;
302
+ try {
303
+ response = await (await registry.loadMiddleware())({
304
+ ...request,
305
+ url: target.url.href,
306
+ headers: target.headers,
307
+ body: target.requestBody
308
+ }, {
309
+ waitUntil: middlewareScope.waitUntil,
310
+ signal: request.signal
311
+ });
312
+ } catch (error) {
313
+ if (request.signal?.aborted) throw error;
314
+ console.error(error);
315
+ responded = new registry.Response("Internal Server Error", { status: 500 });
316
+ return { bodySent: true };
317
+ } finally {
318
+ await middlewareScope.end();
319
+ }
320
+ const result = middlewareResult(response, target.headers, target.url);
321
+ if (result.bodySent) responded = response;
322
+ else headers = result.requestHeaders ?? headers;
323
+ return result;
324
+ };
325
+ const proxy = opened?.proxy ?? true;
326
+ await loadCacheHandlers();
327
+ const resolved = await resolve(request, middlewareBody, invokeMiddleware, proxy);
328
+ shareIncrementalCache(headers);
329
+ const routedHeaders = resolved.resolvedHeaders ?? new registry.Headers();
330
+ if (responded) return finishWithBody(request, responded, endRequest);
331
+ const redirect = redirectStatus(resolved);
332
+ if (redirect !== void 0) return finishWithBody(request, new registry.Response(null, {
333
+ status: redirect,
334
+ headers: routedHeaders
335
+ }), endRequest);
336
+ if (resolved.externalRewrite) {
337
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
338
+ return finishWithBody(request, await registry.network(resolved.externalRewrite, {
339
+ method: request.method,
340
+ headers,
341
+ body: hasBody ? await new registry.Response(body).arrayBuffer() : void 0,
342
+ signal: request.signal
343
+ }), endRequest, routedHeaders);
344
+ }
345
+ const matched = routes$1.get(routing.outputs[resolved.resolvedPathname ?? ""] ?? "");
346
+ const component = nodeRouteFor(opened, matched);
347
+ if (!matched && !component && unrouted === "pass") return { finished: endRequest() };
348
+ const page = component?.page ?? matched?.page ?? notFoundPage;
349
+ const entry = component?.component ?? page;
350
+ const routed = {
351
+ ...request,
352
+ headers,
353
+ body
354
+ };
355
+ const target = resolved.invocationTarget;
356
+ const query = target && appQuery(Object.entries(target.query));
357
+ const rewrittenPath = target !== void 0 && target.pathname !== url.pathname;
358
+ const rewrittenQuery = query !== void 0 && searchOf(query) !== searchOf(appQuery(url.searchParams));
359
+ const namesParam = [...url.searchParams.keys()].some((name) => normalizeNextQueryParam(name) !== null);
360
+ const requestMeta = {
361
+ params: target && matched?.pathname.includes("[") ? paramsOf(matched.pathname, withoutBasePath(target.pathname)) : void 0,
362
+ query: rewrittenPath || rewrittenQuery || namesParam ? query : void 0
363
+ };
364
+ if (target && query && isRSCRequestHeader(headers.get(RSC_HEADER) ?? void 0)) {
365
+ if (rewrittenPath) routedHeaders.set(NEXT_REWRITTEN_PATH_HEADER, withoutBasePath(target.pathname));
366
+ if (rewrittenQuery && !routedHeaders.has(NEXT_REWRITTEN_QUERY_HEADER)) routedHeaders.set(NEXT_REWRITTEN_QUERY_HEADER, searchOf(query));
367
+ }
368
+ const prerenders = build === "production" && !component && matched && matched.page !== notFoundPage ? matched : void 0;
369
+ const prefetch = prerenders?.kind === "page" && headers.get(NEXT_ROUTER_PREFETCH_HEADER) === "1" && isRSCRequestHeader(headers.get(RSC_HEADER) ?? void 0);
370
+ if (prerenders && !prefetch) await recordVerdict(prerenders);
371
+ if (prerenders && isPrerendered(prerenders) && request.method !== "GET" && request.method !== "HEAD" && !getIsPossibleServerAction({
372
+ method: request.method,
373
+ headers
374
+ })) return finishWithBody(request, new registry.Response("Method Not Allowed", {
375
+ status: 405,
376
+ headers: { allow: "GET, HEAD" }
377
+ }), endRequest, routedHeaders);
378
+ const renderPage = async (page, entry, meta = requestMeta) => {
379
+ setServerActions(await serverActionsOf(routed));
380
+ const inline = !isRSCRequestHeader(headers.get(RSC_HEADER) ?? void 0);
381
+ const files = component ? opened?.node?.files ?? [] : [];
382
+ if (pageLoad && inline) await registry.preloadRoute(entry, files, request.signal);
383
+ const [stylesheets, { handler, routeModule }] = await Promise.all([registry.loadStylesheets(entry, inline, files), registry.loadAppPage(entry)]);
384
+ setStylesheets(stylesheets);
385
+ if (component) routeModule.distDir = nodeRouteDistDir;
386
+ const statusCode = page === notFoundPage ? 404 : void 0;
387
+ const respond = () => handleRequest$1(routed, context, handler, meta, {
388
+ statusCode,
389
+ headers: routedHeaders
390
+ });
391
+ if (page === notFoundPage) return respond();
392
+ if (prefetch) return withoutRoute(prerenders, respond);
393
+ return prerenderFirst(prerenders, scope, respond);
394
+ };
395
+ const renderNotFound = async () => finishWithBody(request, await renderPage(notFoundPage, notFoundPage, {}), endRequest, cookiesOf(routedHeaders));
396
+ if (!component && matched?.kind === "route") {
397
+ const handler = await registry.loadRouteHandler(page);
398
+ let response;
399
+ try {
400
+ response = await prerenderFirst(prerenders, scope, () => handleRequest$1(routed, context, handler, requestMeta, { headers: routedHeaders }));
401
+ } catch (error) {
402
+ if (registry.isNoFallbackError(error)) return renderNotFound();
403
+ if (!prerenders || request.signal?.aborted) throw error;
404
+ response = new registry.Response(null, { status: 500 });
405
+ }
406
+ if (prerenders && !(response.status < 400 || response.status === 404)) demoteRoute(prerenders);
407
+ return finishWithBody(request, response, endRequest, cookiesOf(routedHeaders));
408
+ }
409
+ if (prefetch) {
410
+ const pathname = withoutBasePath(target?.pathname ?? url.pathname);
411
+ if (await isUnlisted(prerenders, pathname.length > 1 ? pathname.replace(/\/$/, "") : pathname)) return renderNotFound();
412
+ }
413
+ let response;
414
+ try {
415
+ response = await renderPage(page, entry);
416
+ } catch (error) {
417
+ if (page === notFoundPage || !registry.isNoFallbackError(error)) throw error;
418
+ return renderNotFound();
419
+ }
420
+ return finishWithBody(request, response, endRequest, cookiesOf(routedHeaders));
421
+ } catch (error) {
422
+ scope.endScope();
423
+ throw error;
424
+ }
425
+ }
426
+ function cookiesOf(headers) {
427
+ const cookies = new registry.Headers();
428
+ for (const cookie of headers.getSetCookie()) cookies.append("set-cookie", cookie);
429
+ return cookies;
430
+ }
431
+ function finishWithBody(request, response, onFinish, routedHeaders) {
432
+ let finished;
433
+ let body = null;
434
+ const writing = writingOf(response);
435
+ if (writing) {
436
+ rendering.add(writing.stop);
437
+ body = response.body;
438
+ finished = writing.written.finally(() => {
439
+ rendering.delete(writing.stop);
440
+ return onFinish();
441
+ });
442
+ } else if (response.body) {
443
+ const pipe = new TransformStream(void 0, void 0, { highWaterMark: Infinity });
444
+ const stopping = new AbortController();
445
+ const stop = () => stopping.abort();
446
+ rendering.add(stop);
447
+ body = pipe.readable;
448
+ const source = response.body;
449
+ finished = source.pipeTo(pipe.writable, {
450
+ signal: stopping.signal,
451
+ preventCancel: true
452
+ }).catch(() => source.cancel(new DOMException("The page was left before the server had sent it.", "AbortError"))).catch(() => {}).finally(() => {
453
+ rendering.delete(stop);
454
+ return onFinish();
455
+ });
456
+ } else finished = onFinish();
457
+ const adds = routedHeaders !== void 0 && !routedHeaders.keys().next().done;
458
+ if (writing && !adds && request.method !== "HEAD") return {
459
+ response,
460
+ finished
461
+ };
462
+ let { headers } = response;
463
+ if (routedHeaders) {
464
+ headers = new registry.Headers(routedHeaders);
465
+ response.headers.forEach((value, name) => {
466
+ if (name === "set-cookie") headers.append(name, value);
467
+ else headers.set(name, value);
468
+ });
469
+ }
470
+ return {
471
+ response: new registry.Response(request.method === "HEAD" ? null : body, {
472
+ status: response.status,
473
+ statusText: response.statusText,
474
+ headers
475
+ }),
476
+ finished
477
+ };
478
+ }
479
+ //#endregion
480
+ export { handleRequest, resetCaches, settleRequests, shellErrorAnswer };
@@ -0,0 +1,36 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/static-build.d.ts
4
+ /**
5
+ * The `next.config` of the app as the layers of a static build have it: with
6
+ * each option that is a path of the machine as a path from the root of the
7
+ * project, like `next.config.ts` and `../..`. Next's build reads them, and its
8
+ * server reads none for a request but the cache handlers, which the plugin
9
+ * imports by a module of its own, which the build bundles. A build would
10
+ * otherwise have the directory it was built in, and differ from machine to
11
+ * machine.
12
+ *
13
+ * By the options, not by what a value looks like: `basePath: "/app"` of a
14
+ * project in `/app` is no path of the machine.
15
+ */
16
+ declare function withPathsFromRoot<T>(config: T, root: string): T;
17
+ type NextStaticBuildOptions = {
18
+ /** The Vite environment of each layer. */environments: {
19
+ rsc: string;
20
+ ssr: string;
21
+ browser: string;
22
+ }; /** What the page imports to start the layers that run through a module runner. */
23
+ entries: {
24
+ ssr: string;
25
+ browser: string;
26
+ }; /** The files Next's loaders made for the browser, by the path it asks for. */
27
+ emittedFiles(): {
28
+ pathname: string;
29
+ body: Uint8Array;
30
+ }[]; /** The plugin's module of the rsc layer, which renders the routes: rsc.ts. */
31
+ rscModule: string; /** Whether a module of the rsc layer's build is a file of the host. */
32
+ isHostFile(id: string): boolean;
33
+ };
34
+ declare function nextStaticBuild(options: NextStaticBuildOptions): Plugin;
35
+ //#endregion
36
+ export { NextStaticBuildOptions, nextStaticBuild, withPathsFromRoot };
@@ -0,0 +1,92 @@
1
+ import { normalizePath } from "vite";
2
+ import path from "node:path";
3
+ import { getPluginApi } from "@vitejs/plugin-rsc/plugin";
4
+ import { withStaticBuild } from "@kasperpeulen/vitest-plugin-rsc/internal/static-build";
5
+ //#region src/static-build.ts
6
+ const configPaths = [
7
+ ["configFile"],
8
+ ["outputFileTracingRoot"],
9
+ ["repoRoot"],
10
+ ["turbopack", "root"],
11
+ ["images", "loaderFile"],
12
+ ["cacheHandler"],
13
+ ["cacheHandlers", "*"],
14
+ ["adapterPath"]
15
+ ];
16
+ /**
17
+ * The `next.config` of the app as the layers of a static build have it: with
18
+ * each option that is a path of the machine as a path from the root of the
19
+ * project, like `next.config.ts` and `../..`. Next's build reads them, and its
20
+ * server reads none for a request but the cache handlers, which the plugin
21
+ * imports by a module of its own, which the build bundles. A build would
22
+ * otherwise have the directory it was built in, and differ from machine to
23
+ * machine.
24
+ *
25
+ * By the options, not by what a value looks like: `basePath: "/app"` of a
26
+ * project in `/app` is no path of the machine.
27
+ */
28
+ function withPathsFromRoot(config, root) {
29
+ const copy = JSON.parse(JSON.stringify(config));
30
+ const relativize = (object, [key, ...rest]) => {
31
+ for (const name of key === "*" ? Object.keys(object) : [key]) {
32
+ const value = object[name];
33
+ if (rest.length > 0) {
34
+ if (value && typeof value === "object") relativize(value, rest);
35
+ } else if (typeof value === "string" && path.isAbsolute(value)) object[name] = normalizePath(path.relative(root, value)) || ".";
36
+ }
37
+ };
38
+ for (const keys of configPaths) relativize(copy, keys);
39
+ return copy;
40
+ }
41
+ function nextStaticBuild(options) {
42
+ const { rsc, ssr, browser } = options.environments;
43
+ let manager;
44
+ let reached;
45
+ return {
46
+ name: "vitest-plugin-rsc:next-static-build",
47
+ config(_config, { command }) {
48
+ if (command !== "build") return;
49
+ return withStaticBuild({
50
+ entries: {
51
+ [ssr]: options.entries.ssr,
52
+ [browser]: options.entries.browser
53
+ },
54
+ directives: ["use turbopack: no side effects"],
55
+ strictExecutionOrder: true,
56
+ files: options.emittedFiles,
57
+ clientReferences: { [ssr]: () => reached }
58
+ });
59
+ },
60
+ configResolved(resolved) {
61
+ if (resolved.command !== "build") return;
62
+ manager = getPluginApi(resolved)?.manager;
63
+ if (!resolved.plugins.some((plugin) => plugin.name === "vitest-plugin-rsc:static-build")) throw new Error("vitest-plugin-rsc: vitestPluginNext() needs vitestPluginRSC().");
64
+ },
65
+ buildEnd(error) {
66
+ if (error || this.environment.mode !== "build" || this.environment.name !== rsc) return;
67
+ if (!manager || manager.isScanBuild) return;
68
+ reached = void 0;
69
+ if (!this.getModuleInfo(options.rscModule)) return;
70
+ const references = manager.clientReferenceMetaMap;
71
+ const found = /* @__PURE__ */ new Set();
72
+ const queue = [...this.getModuleIds()].filter((id) => id === options.rscModule || options.isHostFile(id) && !references[id]);
73
+ const seen = new Set(queue);
74
+ for (const id of queue) {
75
+ const reference = references[id];
76
+ if (reference) {
77
+ found.add(reference.referenceKey);
78
+ continue;
79
+ }
80
+ const info = this.getModuleInfo(id);
81
+ for (const imported of [...info?.importedIds ?? [], ...info?.dynamicallyImportedIds ?? []]) {
82
+ if (seen.has(imported)) continue;
83
+ seen.add(imported);
84
+ queue.push(imported);
85
+ }
86
+ }
87
+ reached = found;
88
+ }
89
+ };
90
+ }
91
+ //#endregion
92
+ export { nextStaticBuild, withPathsFromRoot };
@@ -0,0 +1,31 @@
1
+ //#region src/storage.d.ts
2
+ /**
3
+ * The items of the page's `localStorage` and `sessionStorage`. One for each
4
+ * browser context of the app.
5
+ */
6
+ declare class PageStorage {
7
+ readonly localStorage: Map<string, string>;
8
+ readonly sessionStorage: Map<string, string>;
9
+ }
10
+ /** The items of a storage, as a map has them: the page's, or the browser's. */
11
+ type Items = {
12
+ get(key: string): string | undefined;
13
+ set(key: string, value: string): void;
14
+ delete(key: string): boolean;
15
+ has(key: string): boolean;
16
+ clear(): void;
17
+ keys(): Iterable<string>;
18
+ readonly size: number;
19
+ };
20
+ /** The items of a `Storage` of the browser's, as a map. */
21
+ declare function itemsOfStorage(storage: Storage): Items;
22
+ /**
23
+ * A `Storage` of the items that `items()` returns each time it is used. Its
24
+ * items are its properties too, as in a browser: `storage.theme`,
25
+ * `Object.keys(storage)` and `JSON.stringify(storage)` have them, and setting
26
+ * or deleting a property sets or removes an item. A name of `Storage`, like
27
+ * `key` or `length`, hides the item of that name.
28
+ */
29
+ declare function storageOf(items: () => Items): Storage;
30
+ //#endregion
31
+ export { Items, PageStorage, itemsOfStorage, storageOf };