@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
@@ -0,0 +1,193 @@
1
+ import { registry } from "./registry.js";
2
+ import { restoreIncrementalCache } from "./cache.js";
3
+ import { MockedResponse, fromNodeOutgoingHttpHeaders, isDynamicServerError, toNodeOutgoingHttpHeaders } from "./compat/ssr/node-server.js";
4
+ import { Readable } from "@kasperpeulen/vitest-plugin-nextjs/node/stream";
5
+ //#region src/node-server.ts
6
+ const nativeSetTimeout = globalThis.setTimeout;
7
+ const leftBeforeSent = () => new DOMException("The page was left before the server had sent it.", "AbortError");
8
+ /** The `http.IncomingMessage` of a request: a stream of its body, with what Next reads of it. */
9
+ function createNodeRequest(request) {
10
+ const url = new URL(request.url);
11
+ const { body } = request;
12
+ const stream = body instanceof Uint8Array ? Readable.from([body]) : body && Readable.fromWeb(body);
13
+ return Object.assign(stream || Readable.from([]), {
14
+ method: request.method,
15
+ url: url.pathname + url.search,
16
+ headers: {
17
+ host: url.host,
18
+ ...toNodeOutgoingHttpHeaders(request.headers)
19
+ },
20
+ httpVersion: "1.1",
21
+ socket: { encrypted: url.protocol === "https:" }
22
+ });
23
+ }
24
+ const writings = /* @__PURE__ */ new WeakMap();
25
+ /** The `Writing` of a response of `handleRequest()` with a body. */
26
+ const writingOf = (response) => writings.get(response);
27
+ /**
28
+ * The `http.ServerResponse` of a request, which is Next's own stand-in for
29
+ * one, and the `Response` the browser gets of it: once its head is there, while
30
+ * Next goes on to write the body.
31
+ *
32
+ * A request that is left before its head has no response. It lasts until
33
+ * Next's handler is done with it, which can be much later, when the data it
34
+ * waited for comes: its stores have to be there until then.
35
+ */
36
+ function createNodeResponse(request) {
37
+ let controller;
38
+ let headSent = false;
39
+ let wrote = false;
40
+ let finished = false;
41
+ let cancelled = false;
42
+ let sendHead;
43
+ const head = new Promise((resolve) => sendHead = resolve).then(() => headSent = true);
44
+ const encoder = new TextEncoder();
45
+ let ended;
46
+ const bodyWritten = new Promise((resolve) => ended = resolve);
47
+ const body = new ReadableStream({
48
+ start: (c) => void (controller = c),
49
+ cancel() {
50
+ cancelled = true;
51
+ res.destroy();
52
+ ended();
53
+ }
54
+ });
55
+ const stop = () => {
56
+ cancelled = true;
57
+ controller.error(AbortSignal.abort().reason);
58
+ res.destroy();
59
+ ended();
60
+ };
61
+ const res = new MockedResponse({ resWriter(chunk) {
62
+ if (res.destroyed) return false;
63
+ sendHead();
64
+ wrote = headSent = res.headersSent = true;
65
+ controller.enqueue(typeof chunk === "string" ? encoder.encode(chunk) : new Uint8Array(chunk));
66
+ return true;
67
+ } });
68
+ const appendHeader = res.appendHeader.bind(res);
69
+ res.appendHeader = (name, value) => name.toLowerCase() === "location" && res.getHeader(name) === value ? res : appendHeader(name, value);
70
+ const end = res.end.bind(res);
71
+ res.end = ((...args) => {
72
+ end(...args);
73
+ if (res.destroyed) return res;
74
+ if (!wrote) finished = true;
75
+ headSent = res.headersSent = true;
76
+ sendHead();
77
+ return res;
78
+ });
79
+ res._writableState.autoDestroy = false;
80
+ res.on("finish", () => {
81
+ finished = true;
82
+ Object.defineProperty(res, "writableFinished", {
83
+ value: true,
84
+ configurable: true
85
+ });
86
+ controller.close();
87
+ ended();
88
+ nativeSetTimeout(() => res.destroy());
89
+ });
90
+ let failure;
91
+ res.on("error", (error) => failure ??= error);
92
+ res.on("close", () => {
93
+ if (!headSent || finished || cancelled) return;
94
+ controller.error(failure ?? leftBeforeSent());
95
+ ended();
96
+ });
97
+ res.hasStreamed.catch(() => {});
98
+ const { signal } = request;
99
+ const leave = () => res.destroy();
100
+ signal?.addEventListener("abort", leave, { once: true });
101
+ res.once("close", () => signal?.removeEventListener("abort", leave));
102
+ const response = (written) => {
103
+ const sent = head.then(() => {
104
+ const empty = finished && !wrote || [
105
+ 101,
106
+ 204,
107
+ 205,
108
+ 304
109
+ ].includes(res.statusCode);
110
+ const answer = new registry.Response(empty ? null : body, {
111
+ status: res.statusCode,
112
+ statusText: res.statusMessage,
113
+ headers: fromNodeOutgoingHttpHeaders(res.getHeaders())
114
+ });
115
+ if (!empty) writings.set(answer, {
116
+ written: bodyWritten,
117
+ stop
118
+ });
119
+ return answer;
120
+ });
121
+ const settled = written.then(() => {
122
+ if (headSent) return sent;
123
+ res.destroy();
124
+ throw failure ?? leftBeforeSent();
125
+ }, (error) => {
126
+ if (!headSent) {
127
+ res.destroy();
128
+ throw error;
129
+ }
130
+ res.destroy(error ?? /* @__PURE__ */ new Error("The request handler failed."));
131
+ return sent;
132
+ });
133
+ return Promise.race([sent, settled]);
134
+ };
135
+ return {
136
+ res,
137
+ response
138
+ };
139
+ }
140
+ const staticToDynamic = "Page changed from static to dynamic at runtime ";
141
+ const isPrerenderBailout = (error) => isDynamicServerError(error) || error instanceof Error && error.message.startsWith(staticToDynamic);
142
+ const prerenders = [];
143
+ const routerServerContexts = Symbol.for("@next/router-server-methods");
144
+ globalThis[routerServerContexts] = { "": { logErrorWithOriginalStack(error) {
145
+ const prerender = prerenders.at(-1);
146
+ if (!isPrerenderBailout(error)) console.error(error);
147
+ else if (prerender) prerender.bailouts++;
148
+ } } };
149
+ /**
150
+ * Counts the bail-outs that Next's handler reports for the prerender that
151
+ * starts now: also those it answers with its error page itself, where its
152
+ * render of a page recovers from the error (`app-page-runtime.js:1133-1224`).
153
+ * The function it returns ends the count and answers it, once: a count that
154
+ * stays would get the bail-outs of the prerenders after it.
155
+ */
156
+ function countBailouts() {
157
+ const prerender = { bailouts: 0 };
158
+ prerenders.push(prerender);
159
+ return () => {
160
+ const at = prerenders.lastIndexOf(prerender);
161
+ if (at !== -1) prerenders.splice(at, 1);
162
+ return prerender.bailouts;
163
+ };
164
+ }
165
+ function requestMetaOf(request, routed = {}) {
166
+ return {
167
+ ...routed.params && { params: routed.params },
168
+ ...routed.query && { query: routed.query },
169
+ initURL: request.url,
170
+ initProtocol: new URL(request.url).protocol.slice(0, -1)
171
+ };
172
+ }
173
+ /**
174
+ * One request, by the request handler Next's build makes for its route:
175
+ * `templates/app-page-runtime` for a page and `templates/app-route` for a
176
+ * route handler, as `next start` calls them.
177
+ */
178
+ async function handleRequest(request, context, handler, routed, { statusCode, headers } = {}) {
179
+ const { res, response } = createNodeResponse(request);
180
+ if (statusCode !== void 0) res.statusCode = statusCode;
181
+ headers?.forEach((value, name) => {
182
+ if (name !== "set-cookie") res.setHeader(name, value);
183
+ });
184
+ return response(handler(createNodeRequest(request), res, {
185
+ waitUntil: context.waitUntil,
186
+ requestMeta: {
187
+ ...requestMetaOf(request, routed),
188
+ incrementalCache: globalThis.__incrementalCache
189
+ }
190
+ }).finally(restoreIncrementalCache));
191
+ }
192
+ //#endregion
193
+ export { countBailouts, handleRequest, isPrerenderBailout, writingOf };
@@ -0,0 +1,26 @@
1
+ import { NextLayer, NextProject } from "./project.js";
2
+
3
+ //#region src/optimizer-cache-key.d.ts
4
+ type OptimizerInputs = Pick<NextProject, "aliases" | "appAliases" | "dependencyCompileOptions" | "newExports" | "version">;
5
+ /**
6
+ * What the project gives the optimizer's plugins of a layer: Next's alias
7
+ * table and the app's, which the resolver picks from by the importer, and the
8
+ * options of Next's transform for a dependency. The tables in their order,
9
+ * not sorted: the resolver takes the first entry that matches, and a prefix
10
+ * like `next` can match where a later one would. The order is the code's,
11
+ * the same for every run. And the stubs of the stand-ins, of the version of
12
+ * Next.
13
+ */
14
+ declare function projectKey(project: OptimizerInputs, layer: NextLayer): string;
15
+ /**
16
+ * The code of a directory of the plugin: the contents of every file of it.
17
+ * The contents, not the version, which a build of this repository keeps, and
18
+ * not the times of the files, which every build changes. All of the code,
19
+ * not just the modules that compile a dependency: a bundle of `dist` mixes
20
+ * modules, and what those import is just as much the compile.
21
+ */
22
+ declare function codeKey(dir: string): string;
23
+ /** For the `define` of the optimizer of a layer, which Vite keys its cache on. */
24
+ declare function optimizerCacheKey(project: OptimizerInputs, layer: NextLayer): Record<string, string>;
25
+ //#endregion
26
+ export { codeKey, optimizerCacheKey, projectKey };
@@ -0,0 +1,73 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import fs from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/optimizer-cache-key.ts
7
+ const hash = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
8
+ /**
9
+ * What the project gives the optimizer's plugins of a layer: Next's alias
10
+ * table and the app's, which the resolver picks from by the importer, and the
11
+ * options of Next's transform for a dependency. The tables in their order,
12
+ * not sorted: the resolver takes the first entry that matches, and a prefix
13
+ * like `next` can match where a later one would. The order is the code's,
14
+ * the same for every run. And the stubs of the stand-ins, of the version of
15
+ * Next.
16
+ */
17
+ function projectKey(project, layer) {
18
+ return hash(JSON.stringify([
19
+ Object.entries(project.aliases[layer]),
20
+ Object.entries(project.appAliases[layer]),
21
+ project.dependencyCompileOptions[layer],
22
+ project.newExports,
23
+ project.version
24
+ ]));
25
+ }
26
+ const isCode = (file) => /\.[cm]?[jt]sx?$/.test(file) && !/\.test\.[cm]?[jt]sx?$/.test(file) && !/\.d\.[cm]?ts$/.test(file);
27
+ /**
28
+ * The code of a directory of the plugin: the contents of every file of it.
29
+ * The contents, not the version, which a build of this repository keeps, and
30
+ * not the times of the files, which every build changes. All of the code,
31
+ * not just the modules that compile a dependency: a bundle of `dist` mixes
32
+ * modules, and what those import is just as much the compile.
33
+ */
34
+ function codeKey(dir) {
35
+ const content = createHash("sha256");
36
+ const files = fs.readdirSync(dir, {
37
+ recursive: true,
38
+ encoding: "utf8"
39
+ }).map((file) => file.split(path.sep).join("/")).filter(isCode).sort();
40
+ for (const file of files) {
41
+ const full = path.join(dir, file);
42
+ if (!fs.statSync(full).isFile()) continue;
43
+ content.update(`${file}\0`).update(fs.readFileSync(full)).update("\0");
44
+ }
45
+ return content.digest("hex").slice(0, 16);
46
+ }
47
+ function ownCodeDirs() {
48
+ const file = fileURLToPath(import.meta.url);
49
+ for (let dir = path.dirname(file); dir !== path.dirname(dir); dir = path.dirname(dir)) {
50
+ const manifest = path.join(dir, "package.json");
51
+ if (!fs.existsSync(manifest)) continue;
52
+ const { name } = JSON.parse(fs.readFileSync(manifest, "utf8"));
53
+ if (name !== "@kasperpeulen/vitest-plugin-nextjs") return [];
54
+ const core = path.dirname(createRequire(import.meta.url).resolve("@kasperpeulen/vitest-plugin-rsc/package.json"));
55
+ return [dir, core].flatMap((pkg) => ["src", "dist"].map((code) => path.join(pkg, code))).filter((code) => fs.statSync(code, { throwIfNoEntry: false })?.isDirectory());
56
+ }
57
+ return [];
58
+ }
59
+ let ownCodeKey;
60
+ /** For the `define` of the optimizer of a layer, which Vite keys its cache on. */
61
+ function optimizerCacheKey(project, layer) {
62
+ if (ownCodeKey === void 0) try {
63
+ ownCodeKey = hash(ownCodeDirs().map((dir) => `${path.basename(dir)}:${codeKey(dir)}`).join(","));
64
+ } catch {
65
+ ownCodeKey = "";
66
+ }
67
+ return {
68
+ __vitest_plugin_rsc_project__: JSON.stringify(projectKey(project, layer)),
69
+ __vitest_plugin_rsc_code__: JSON.stringify(ownCodeKey)
70
+ };
71
+ }
72
+ //#endregion
73
+ export { codeKey, optimizerCacheKey, projectKey };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,6 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { addOwnPackage } from "@kasperpeulen/vitest-plugin-rsc/internal/own-packages";
3
+ //#region src/own-package.ts
4
+ addOwnPackage("@kasperpeulen/vitest-plugin-nextjs", fileURLToPath(new URL("./", import.meta.url)));
5
+ //#endregion
6
+ export {};
@@ -0,0 +1,33 @@
1
+ //#region src/page-store.d.ts
2
+ type Cookie = {
3
+ value: string;
4
+ httpOnly: boolean;
5
+ };
6
+ /**
7
+ * What the browser's requests to the app carry, besides what a browser adds
8
+ * itself. One for the document, from what the test opened until the test
9
+ * ends: `cleanup()` makes a new one.
10
+ */
11
+ declare class PageStore {
12
+ #private;
13
+ /** The `headers` of what the test opened, until it opens something else. */
14
+ headers: Headers;
15
+ /** The app's cookies, by name, with the values the server wrote. */
16
+ readonly cookies: Map<string, Cookie>;
17
+ /** Sets the cookies of a `cookie` header, as a `Set-Cookie` of each would. */
18
+ seed(cookie: string): void;
19
+ /** Sets and deletes cookies by the `Set-Cookie` headers of a response of the app. */
20
+ receive(setCookies: Iterable<string>): void;
21
+ /**
22
+ * Sets or deletes a cookie as a script of the page does, with
23
+ * `document.cookie`. As in a browser, a script cannot write an `HttpOnly`
24
+ * cookie, nor change or delete one.
25
+ */
26
+ write(setCookie: string): void;
27
+ /** The `cookie` header of a request: empty without cookies. */
28
+ cookieHeader(): string;
29
+ /** What `document.cookie` has: the cookies that are not `HttpOnly`. */
30
+ documentCookie(): string;
31
+ }
32
+ //#endregion
33
+ export { PageStore };
@@ -0,0 +1,80 @@
1
+ //#region src/page-store.ts
2
+ /** A name and a value: of a `cookie` header, or of a `Set-Cookie` before its first `;`. */
3
+ function pairOf(text) {
4
+ const at = text.indexOf("=");
5
+ return at === -1 ? ["", text.trim()] : [text.slice(0, at).trim(), text.slice(at + 1).trim()];
6
+ }
7
+ /** The name of an attribute of a `Set-Cookie`, in lower case, and its value. */
8
+ function attributeOf(text) {
9
+ const at = text.indexOf("=");
10
+ return at === -1 ? [text.trim().toLowerCase(), ""] : [text.slice(0, at).trim().toLowerCase(), text.slice(at + 1).trim()];
11
+ }
12
+ function expiresAt(value) {
13
+ if (!/\d{1,2}:\d{1,2}:\d{1,2}/.test(value)) return void 0;
14
+ if (!/jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/i.test(value)) return void 0;
15
+ const time = Date.parse(value);
16
+ return Number.isNaN(time) ? void 0 : time;
17
+ }
18
+ function deletes(attributes) {
19
+ let maxAge;
20
+ let expires;
21
+ for (const [name, value] of attributes.map(attributeOf)) {
22
+ if (name === "max-age" && /^-?\d+$/.test(value)) maxAge = Number(value);
23
+ if (name === "expires") expires = expiresAt(value) ?? expires;
24
+ }
25
+ if (maxAge !== void 0) return maxAge <= 0;
26
+ return expires !== void 0 && expires <= Date.now();
27
+ }
28
+ /** The cookies as a `cookie` header, or as `document.cookie` has them. */
29
+ function serialize(cookies) {
30
+ return Array.from(cookies, ([name, { value }]) => name ? `${name}=${value}` : value).join("; ");
31
+ }
32
+ /**
33
+ * What the browser's requests to the app carry, besides what a browser adds
34
+ * itself. One for the document, from what the test opened until the test
35
+ * ends: `cleanup()` makes a new one.
36
+ */
37
+ var PageStore = class {
38
+ /** The `headers` of what the test opened, until it opens something else. */
39
+ headers = new Headers();
40
+ /** The app's cookies, by name, with the values the server wrote. */
41
+ cookies = /* @__PURE__ */ new Map();
42
+ /** Sets the cookies of a `cookie` header, as a `Set-Cookie` of each would. */
43
+ seed(cookie) {
44
+ this.receive(cookie.split(";"));
45
+ }
46
+ /** Sets and deletes cookies by the `Set-Cookie` headers of a response of the app. */
47
+ receive(setCookies) {
48
+ for (const setCookie of setCookies) this.#set(setCookie, false);
49
+ }
50
+ /**
51
+ * Sets or deletes a cookie as a script of the page does, with
52
+ * `document.cookie`. As in a browser, a script cannot write an `HttpOnly`
53
+ * cookie, nor change or delete one.
54
+ */
55
+ write(setCookie) {
56
+ this.#set(setCookie, true);
57
+ }
58
+ /** The `cookie` header of a request: empty without cookies. */
59
+ cookieHeader() {
60
+ return serialize(this.cookies);
61
+ }
62
+ /** What `document.cookie` has: the cookies that are not `HttpOnly`. */
63
+ documentCookie() {
64
+ return serialize(Array.from(this.cookies).filter(([, { httpOnly }]) => !httpOnly));
65
+ }
66
+ #set(setCookie, byScript) {
67
+ const [pair = "", ...attributes] = setCookie.split(";");
68
+ const [name, value] = pairOf(pair);
69
+ if (!name && !value) return;
70
+ const httpOnly = attributes.some((attribute) => attributeOf(attribute)[0] === "httponly");
71
+ if (byScript && (httpOnly || this.cookies.get(name)?.httpOnly)) return;
72
+ if (deletes(attributes)) this.cookies.delete(name);
73
+ else this.cookies.set(name, {
74
+ value,
75
+ httpOnly
76
+ });
77
+ }
78
+ };
79
+ //#endregion
80
+ export { PageStore };
@@ -0,0 +1,22 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/page-timers.d.ts
4
+ /**
5
+ * Those uses, to the module runner's. Also for the dependency optimizer of
6
+ * the browser layer, by its `transform.define`: the bundler replaces them in
7
+ * every module of the pre-bundle, natively and in parallel, and the result
8
+ * stays on disk, where the plugin below would compile each pre-bundled file
9
+ * again in every run. None of them is a file of the host, and the packages
10
+ * of the host are not in it (`vitest-plugin-rsc:next-host-packages`).
11
+ */
12
+ declare const pageTimersDefine: {
13
+ [k: string]: string;
14
+ };
15
+ /**
16
+ * For the modules of the browser layer, by the name of its environment, but
17
+ * those of a file that `isHostFile()`, and the pre-bundled dependencies,
18
+ * which have `pageTimersDefine`.
19
+ */
20
+ declare function pageTimersPlugin(environment: string, isHostFile: (file: string) => boolean): Plugin;
21
+ //#endregion
22
+ export { pageTimersDefine, pageTimersPlugin };
@@ -0,0 +1,47 @@
1
+ import { fileOfModule } from "./client-files.js";
2
+ import { runnerGlobalNames, timersKey } from "@kasperpeulen/vitest-plugin-rsc/internal/timers";
3
+ import { normalizePath, transformWithOxc } from "vite";
4
+ import { ssrImportMetaKey } from "vite/module-runner";
5
+ //#region src/page-timers.ts
6
+ /**
7
+ * Those uses, to the module runner's. Also for the dependency optimizer of
8
+ * the browser layer, by its `transform.define`: the bundler replaces them in
9
+ * every module of the pre-bundle, natively and in parallel, and the result
10
+ * stays on disk, where the plugin below would compile each pre-bundled file
11
+ * again in every run. None of them is a file of the host, and the packages
12
+ * of the host are not in it (`vitest-plugin-rsc:next-host-packages`).
13
+ */
14
+ const pageTimersDefine = Object.fromEntries(runnerGlobalNames.map((name) => [name, `${ssrImportMetaKey}.${timersKey}.${name}`]));
15
+ const mentionsTimer = new RegExp(`\\b(?:${runnerGlobalNames.join("|")})\\b`);
16
+ /**
17
+ * For the modules of the browser layer, by the name of its environment, but
18
+ * those of a file that `isHostFile()`, and the pre-bundled dependencies,
19
+ * which have `pageTimersDefine`.
20
+ */
21
+ function pageTimersPlugin(environment, isHostFile) {
22
+ return {
23
+ name: "vitest-plugin-rsc:next-page-timers",
24
+ enforce: "post",
25
+ applyToEnvironment: ({ name }) => name === environment,
26
+ transform: {
27
+ filter: { code: mentionsTimer },
28
+ async handler(code, id) {
29
+ const { environment } = this;
30
+ if (environment.mode === "dev" && environment.depsOptimizer?.isOptimizedDepFile(id)) return;
31
+ const file = fileOfModule(id);
32
+ if (file !== void 0 && isHostFile(file)) return;
33
+ try {
34
+ return await transformWithOxc(code, normalizePath(id), {
35
+ lang: "js",
36
+ define: pageTimersDefine
37
+ });
38
+ } catch (error) {
39
+ const reason = String(error instanceof Error ? error.message : error);
40
+ this.warn(`vitest-plugin-rsc: the timers of ${id} keep running after its page is left, it does not parse as JavaScript. ${reason.replace(/\s+/g, " ")}`);
41
+ }
42
+ }
43
+ }
44
+ };
45
+ }
46
+ //#endregion
47
+ export { pageTimersDefine, pageTimersPlugin };
package/dist/page.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ import { Opened } from "./registry.js";
2
+ import { PageStore } from "./page-store.js";
3
+ import { NodeOnPage } from "./node-on-page.js";
4
+ import { HistoryEntry } from "./document.js";
5
+ import { ShellErrorAnswer } from "./ssr.js";
6
+
7
+ //#region src/page.d.ts
8
+ /**
9
+ * A navigation without a page of the app, which has the router, under
10
+ * Vitest: a component that a test file with `"use client"` rendered has none.
11
+ */
12
+ declare function noRouter(url: URL): Error;
13
+ /**
14
+ * A page of the app, from the first line of its load until it is left, with
15
+ * the node of `renderServer()` on it, and where it reports its errors. Its
16
+ * load makes it part by part, and each part that would outlive the page says
17
+ * how it is released. Leaving stops the load at once, and releases the
18
+ * parts, the last first.
19
+ */
20
+ declare class Page {
21
+ #private;
22
+ readonly errors: unknown[];
23
+ readonly nodeOnPage?: NodeOnPage | undefined;
24
+ /** Whether the page renders in the app kept from the page before: see `kept`. */
25
+ readonly inKeptApp: boolean;
26
+ /** Aborts as the page is left: what the load does takes it. */
27
+ readonly signal: AbortSignal;
28
+ constructor(errors: unknown[], nodeOnPage?: NodeOnPage | undefined, /** Whether the page renders in the app kept from the page before: see `kept`. */
29
+
30
+ inKeptApp?: boolean);
31
+ /** Whether the page is open: from when its client starts until it is left. */
32
+ get open(): boolean;
33
+ /**
34
+ * Runs `release` as the page is left: before what the load made before
35
+ * it. As `defer()` of a `DisposableStack`.
36
+ */
37
+ defer(release: () => void): void;
38
+ /**
39
+ * Opens the page: its client starts. Leaving waits for `started` to
40
+ * settle, at most `startTimeout`, before it releases what the page holds.
41
+ * `stopped` runs once the start is over for the page: once `started` has
42
+ * settled, or once leaving stops waiting for it.
43
+ */
44
+ starts(started: Promise<unknown>, stopped: () => void): void;
45
+ /**
46
+ * Leaves the page: `by` is what leaves it, for its node. Stops its load at
47
+ * once. Resolves once what the page holds is released, or rejects with the
48
+ * first error of a release, after every release has run. A page still
49
+ * starting after `startTimeout` is released once it has stopped, and
50
+ * `reportError()` gets that error: leaving resolves without it. Once:
51
+ * leaving it again gets the same promise.
52
+ */
53
+ leave(by: string): Promise<void>;
54
+ }
55
+ /**
56
+ * Leaves the page of the kept app that is open, as `leavePage()` does with
57
+ * `by`, and then the kept app: a test file ends with them. The app goes also
58
+ * after a page that could not be left, and the first error rejects.
59
+ */
60
+ declare function leaveKeptAppWithPage(by: string): Promise<void>;
61
+ /** The page that is open: see `Page.open`. */
62
+ declare function currentPage(): Page | undefined;
63
+ type Opening = {
64
+ container?: Element;
65
+ opened: Opened;
66
+ headers: Headers;
67
+ cookie: string | null; /** For a node: what `rerender()` waits for. */
68
+ nodeOnPage?: NodeOnPage; /** What stops the load: see `RequestOptions.signal` in index.ts. */
69
+ signal?: AbortSignal; /** Where the page, and the pages the app loads from it, report their errors. */
70
+ errors: unknown[];
71
+ /**
72
+ * Renders the page in the app of the page before, if it is kept, and else
73
+ * keeps the app it starts for the pages after it: see `kept`. A page of
74
+ * `keepApp` without HTML of the server.
75
+ */
76
+ inKeptApp?: boolean;
77
+ };
78
+ type PageDocument = {
79
+ response: Response;
80
+ html: string;
81
+ after?: HistoryEntry;
82
+ payload?: Uint8Array<ArrayBuffer>;
83
+ };
84
+ declare function loadPage(url: URL, request: RequestInit | PageDocument, from: PageStore, opening?: Opening): Promise<Response>;
85
+ /** The Flight payload in the scripts of a document of Next. */
86
+ declare function payloadInDocument(html: string): Uint8Array;
87
+ /**
88
+ * How the server would have answered the document of the page of a Flight
89
+ * payload, had it rendered its HTML, by the errors in the payload (see
90
+ * `shellErrorAnswer()` in ssr.ts): the first `redirect()`, wherever it is in
91
+ * the tree, as Next's router follows it (its RedirectBoundary), else the
92
+ * status of the first other error. Undefined for a payload without an error.
93
+ * A path of the app's has the app's `basePath` before it, which `redirect()`
94
+ * takes without it, as Next's server puts it in the `Location` of the 307 of
95
+ * a document.
96
+ */
97
+ declare function renderFailureOf(payload: Uint8Array, answerOf: (digest: string) => ShellErrorAnswer): ShellErrorAnswer | undefined;
98
+ /**
99
+ * A navigation of the app without its router, as a browser makes it: a
100
+ * `<form>`, an `<a>` that React does not handle, `location.assign()`. The
101
+ * page that is open stays until the answer is there, and for good when the
102
+ * answer is a download (downloads.ts). Else the answer is the next page,
103
+ * which needs a page of the app to navigate in. `downloadRequest` is the
104
+ * navigation's, and `from` the store of the browser context it is in. With
105
+ * `push` the next page gets an entry of the history of its own, as after a
106
+ * link or a form in a browser. Else it takes the entry of the page, as
107
+ * after a reload or `location.replace()`.
108
+ */
109
+ declare function navigate(url: URL, init: RequestInit, downloadRequest: string | null, from: PageStore, push: boolean): Promise<void>;
110
+ declare function leavePage(by: string): Promise<void>;
111
+ //#endregion
112
+ export { Page, currentPage, leaveKeptAppWithPage, leavePage, loadPage, navigate, noRouter, payloadInDocument, renderFailureOf };