@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,176 @@
1
+ import { NextLayer } from "./project.js";
2
+ import { Plugin, transformWithOxc } from "vite";
3
+ import * as _$magic_string0 from "magic-string";
4
+
5
+ //#region src/server-code.d.ts
6
+ type Compiled = Pick<Awaited<ReturnType<typeof transformWithOxc>>, "code" | "map">;
7
+ /** Compiles JavaScript as server code. Returns nothing for code that stays as it is. */
8
+ declare function compileServerCode(code: string, id: string, registry: string, define?: Record<string, string>): Promise<Compiled | undefined>;
9
+ type ServerCodeOptions = {
10
+ /**
11
+ * Modules that have to know they run in a browser: glob patterns, relative
12
+ * to the project root. They keep the browser's `typeof window` and `fetch`,
13
+ * as the test files and setup files of the Vitest config do. List a helper of
14
+ * the tests, or a package they use on the page, that asks `typeof window`
15
+ * before it works on the page.
16
+ *
17
+ * Files are matched by their real path, which for a package is seldom
18
+ * `node_modules/<name>` under the root. So start a pattern for one with `**`.
19
+ *
20
+ * @example ["test/**", "**\/node_modules/@testing-library/**"]
21
+ */
22
+ browserModules?: string | RegExp | (string | RegExp)[];
23
+ /**
24
+ * What runs the app, when that is not Vitest. Under Vitest the plugin knows
25
+ * the host's files from its config, the test files and the setup files, and
26
+ * its packages, `vitest` and `@vitest/*`. Another host says here what is its
27
+ * own. A file of the host is not server code: it keeps the browser's
28
+ * `typeof window` and `fetch`, and Next's compiler does not run on
29
+ * it. With `"use client"` it is a module of the browser layer. A package of
30
+ * the host is the one instance the page has, also in the browser layer.
31
+ */
32
+ host?: {
33
+ /**
34
+ * The files of the host, like the ones it renders: glob patterns,
35
+ * relative to the project root. What a test file is to Vitest.
36
+ *
37
+ * @example ["host/**", "src/**\/*.demo.tsx"]
38
+ */
39
+ files?: string | RegExp | (string | RegExp)[];
40
+ /**
41
+ * The packages of the host, by name. A name that ends in `/*` is every
42
+ * package of that scope the project has, or every module under that path
43
+ * of a package, like `host-kit/internal/*`. A module of a package, like
44
+ * `host-kit/test`, is the page's own, and the package's other modules
45
+ * are not, but the files of the whole package are the host's runtime in
46
+ * the rsc layer. What they depend on is the host's too, but for this
47
+ * plugin and what it depends on, which a framework of the host can
48
+ * depend on.
49
+ *
50
+ * Not something the plugin can read off the files: a file of the host
51
+ * with `"use client"` imports `react` and `host-kit/test` the same way,
52
+ * and the first has to be the browser layer's React, where the second
53
+ * has to be the page's own module, whose spies the host shows. What
54
+ * `vitest` is to a test file. The same list keeps the host's runtime
55
+ * the browser's in the rsc layer, where every other package is server
56
+ * code.
57
+ *
58
+ * A package of the list that the browser layer imports only from inside
59
+ * another package reaches the rsc layer through the page, which no scan
60
+ * of Vite's reads: list it in `optimizeDeps.include` too, or Vite
61
+ * pre-bundles it as the first page loads.
62
+ *
63
+ * @example ["host-kit/test", "host-kit/preview"]
64
+ */
65
+ packages?: string[];
66
+ };
67
+ };
68
+ type Warns = {
69
+ warn(message: string): void;
70
+ };
71
+ /**
72
+ * How the server carries the stores of `AsyncLocalStorage` across an `await`
73
+ * and from one step of a render to the next: `compiled`, in a browser, which
74
+ * carries nothing, so the async functions of the server and React's Flight
75
+ * server are compiled to (async-local-storage-transform.ts); or `native`, in
76
+ * a worker of Node.js, whose `AsyncLocalStorage` is Node's own (the package's
77
+ * `async-hooks` under the `node` condition), which needs none of it.
78
+ */
79
+ type AsyncContext = "compiled" | "native";
80
+ declare function createServerCode(registry: string, options?: ServerCodeOptions): {
81
+ isServerCode: (file: string, layer: NextLayer) => boolean;
82
+ isHostFile: (file: string) => boolean;
83
+ isHostPackageFile: (file: string) => boolean;
84
+ isTestFile: (file: string) => boolean;
85
+ /**
86
+ * Whether an import is of a package of the host, by its name: `vitest`,
87
+ * `vitest/browser`, `@vitest/spy`. Not what such a package depends on.
88
+ */
89
+ isHostPackage(specifier: string): boolean;
90
+ /**
91
+ * Whether a source file is code of the app in a layer: what Next's build
92
+ * compiles. Not in the rsc layer: what is the browser's there, the test
93
+ * files and the `browserModules`. And in no layer a file of the host,
94
+ * which with `"use client"` is a module of the browser layer: it is the
95
+ * host's to compile, as any test file of Vitest, with Vite's own
96
+ * transforms and the host's config, not with Next's.
97
+ */
98
+ isAppCode(file: string, layer: NextLayer): boolean; /** For the `define` of the optimizer of a server layer, which Vite keys its cache on. */
99
+ readonly cacheKey: {
100
+ __vitest_plugin_rsc_browser_modules__: string;
101
+ __vitest_plugin_rsc_modules_with_stores__: string;
102
+ __vitest_plugin_rsc_async_context__: string;
103
+ };
104
+ /**
105
+ * The same for the browser layer, which pre-bundles the packages of the
106
+ * host as the page's own: Vite does not key its cache on the plugins of
107
+ * the optimizer.
108
+ */
109
+ browserCacheKey: {
110
+ __vitest_plugin_rsc_host_packages__: string;
111
+ }; /** For a module this plugin generates, with the constants of its layer. */
112
+ compile: (code: string, id: string, define: Record<string, string>) => Promise<string>;
113
+ /**
114
+ * Call once the root of the project is known, and where the server runs:
115
+ * see `AsyncContext`.
116
+ */
117
+ configure(root: string, carried?: AsyncContext): void; /** Call with what a Vitest config says is a test file or a setup file. */
118
+ addTestFiles(matches: (file: string) => boolean): void;
119
+ /**
120
+ * What a dependency of a server layer needs on top of `compileServerCode()`
121
+ * to keep its stores across an `await` and a render, for a static build,
122
+ * which has no optimizer: see `optimizerDefine()`, `optimizerPlugin()` and
123
+ * `asyncFunctionsOptimizerPlugin()`.
124
+ */
125
+ forStores(context: Warns, code: string, id: string, layer: NextLayer): {
126
+ code: string;
127
+ define: {
128
+ queueMicrotask: string;
129
+ };
130
+ } | {
131
+ code: string;
132
+ define: {};
133
+ };
134
+ /**
135
+ * For the dependency optimizer of a layer: what the bundler replaces in
136
+ * every module of the pre-bundle, by its `transform.define`, natively and
137
+ * in parallel. In the ssr layer every dependency is server code
138
+ * (`isServerCode()`), and of this plugin's own files the pre-bundle has
139
+ * only its stand-ins of the modules of Node.js and of `fastCrypto`: those
140
+ * take the server's timers too, as Node's own modules do. The bundler
141
+ * also reaches a dependency in TypeScript or JSX, which oxc does not take
142
+ * as JavaScript. The rsc layer pre-bundles the host's packages with the
143
+ * app's, so there it is `optimizerPlugin()`, file by file.
144
+ */
145
+ optimizerDefine(layer: NextLayer): Record<string, string>;
146
+ /**
147
+ * For the dependency optimizer of the rsc layer: its pre-bundled
148
+ * dependencies that are server code.
149
+ */
150
+ optimizerPlugin(): {
151
+ name: string;
152
+ transform(this: {
153
+ warn(message: string): void;
154
+ }, code: string, id: string): Promise<Compiled | undefined>;
155
+ };
156
+ /**
157
+ * The async functions that have to read, after an `await`, a store Next
158
+ * entered for them, or left for them with `exit()`: see
159
+ * async-local-storage-transform.ts. For the dependency optimizer of a
160
+ * server layer, the modules of Next's server in `nextModulesWithStores`
161
+ * that are that layer's. The bundler hands it each file of a dependency
162
+ * it pre-bundles, by its path, so a module inside the bundle of
163
+ * `app-page/module` is one.
164
+ */
165
+ asyncFunctionsOptimizerPlugin(layer: NextLayer): {
166
+ name: string;
167
+ transform(this: Warns, code: string, id: string): {
168
+ code: string;
169
+ map: _$magic_string0.SourceMap;
170
+ } | undefined;
171
+ }; /** And the source files of the app in the `rsc` layer, by the name of its environment. */
172
+ asyncFunctionsPlugin(environment: string): Plugin; /** For the source files of the server layers, by the name of their environment. */
173
+ plugin(environments: Record<string, NextLayer>): Plugin;
174
+ };
175
+ //#endregion
176
+ export { AsyncContext, ServerCodeOptions, compileServerCode, createServerCode };
@@ -0,0 +1,357 @@
1
+ import { nextModulesWithStores, vendoredFlightServer, withStoresFiles } from "./compat/paths.js";
2
+ import "./own-package.js";
3
+ import { createFilter, normalizePath, transformWithOxc } from "vite";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { isOwnFile } from "@kasperpeulen/vitest-plugin-rsc/internal/own-packages";
8
+ import { transformAsyncFunctions } from "@kasperpeulen/vitest-plugin-rsc/internal/async-local-storage-transform";
9
+ //#region src/server-code.ts
10
+ /** Globals of a browser that a server does not have. */
11
+ const browserGlobals = [
12
+ "window",
13
+ "document",
14
+ "location",
15
+ "localStorage",
16
+ "sessionStorage"
17
+ ];
18
+ /** Globals a server has too, but its own. */
19
+ const serverGlobals = [
20
+ "Request",
21
+ "Response",
22
+ "fetch",
23
+ "setImmediate",
24
+ "clearImmediate",
25
+ "setTimeout",
26
+ "clearTimeout"
27
+ ];
28
+ const mentionsServerGlobal = new RegExp(`\\b(?:${serverGlobals.join("|")})\\b`);
29
+ const mentionsTypeof = /\btypeof\b/;
30
+ const mentionsBrowserGlobal = new RegExp(`\\b(?:${browserGlobals.join("|")})\\b`);
31
+ const serverGlobalsOf = (registry) => Object.fromEntries(serverGlobals.flatMap((name) => [[name, `${registry}.${name}`], [`globalThis.${name}`, `${registry}.${name}`]]));
32
+ const browserTypeofs = Object.fromEntries(browserGlobals.map((name) => [`typeof ${name}`, "\"undefined\""]));
33
+ /** Compiles JavaScript as server code. Returns nothing for code that stays as it is. */
34
+ async function compileServerCode(code, id, registry, define = {}) {
35
+ const defines = {
36
+ ...define,
37
+ ...mentionsServerGlobal.test(code) && serverGlobalsOf(registry),
38
+ ...mentionsTypeof.test(code) && mentionsBrowserGlobal.test(code) && browserTypeofs
39
+ };
40
+ if (Object.keys(defines).length === 0) return;
41
+ return transformWithOxc(code, id, {
42
+ lang: "js",
43
+ define: defines
44
+ });
45
+ }
46
+ function* nodeModulesOf(from) {
47
+ for (let dir = from;; dir = path.dirname(dir)) {
48
+ yield path.join(dir, "node_modules");
49
+ if (dir === path.dirname(dir)) return;
50
+ }
51
+ }
52
+ function findPackage(name, from) {
53
+ for (const nodeModules of nodeModulesOf(from)) {
54
+ const candidate = path.join(nodeModules, name);
55
+ if (fs.existsSync(path.join(candidate, "package.json"))) return fs.realpathSync(candidate);
56
+ }
57
+ }
58
+ function dependenciesOf(dir) {
59
+ const file = path.join(dir, "package.json");
60
+ if (!fs.existsSync(file)) return [];
61
+ const { dependencies = {} } = JSON.parse(fs.readFileSync(file, "utf8"));
62
+ return Object.keys(dependencies);
63
+ }
64
+ function ownPackageDir() {
65
+ let dir = path.dirname(fileURLToPath(import.meta.url));
66
+ while (!fs.existsSync(path.join(dir, "package.json")) && dir !== path.dirname(dir)) dir = path.dirname(dir);
67
+ return fs.realpathSync(dir);
68
+ }
69
+ function ownPackageDirs() {
70
+ const own = ownPackageDir();
71
+ const core = findPackage("@kasperpeulen/vitest-plugin-rsc", own);
72
+ const dirs = [own, ...core ? [core] : []];
73
+ const dependencies = dirs.flatMap((dir) => dependenciesOf(dir).map((name) => findPackage(name, dir)));
74
+ return /* @__PURE__ */ new Set([...dirs, ...dependencies.filter((found) => found !== void 0)]);
75
+ }
76
+ function findPackagesWithDependencies(names, root) {
77
+ const dirs = /* @__PURE__ */ new Set();
78
+ const named = new Set(names);
79
+ const appDirs = new Set(dependenciesOf(root).map((name) => findPackage(name, root)).filter((dir) => dir !== void 0));
80
+ const own = ownPackageDirs();
81
+ const visit = (name, from) => {
82
+ const dir = findPackage(name, from);
83
+ if (!dir || own.has(dir) || dirs.has(normalizePath(dir))) return;
84
+ if (!named.has(name) && appDirs.has(dir)) return;
85
+ dirs.add(normalizePath(dir));
86
+ for (const dependency of dependenciesOf(dir)) visit(dependency, dir);
87
+ };
88
+ for (const name of named) visit(name, root);
89
+ return dirs;
90
+ }
91
+ const hostPackages = (packages) => [
92
+ "vitest",
93
+ "@vitest/*",
94
+ ...packages
95
+ ];
96
+ const packageNameOf = (specifier) => /^(?:@[^/]+\/)?[^/]+/.exec(specifier)[0];
97
+ function* hostPackageNames(root, packages) {
98
+ for (const name of ["vite", ...hostPackages(packages)]) {
99
+ if (!/^@[^/]+\/\*$/.test(name)) {
100
+ yield packageNameOf(name);
101
+ continue;
102
+ }
103
+ const scope = name.slice(0, -2);
104
+ for (const nodeModules of nodeModulesOf(root)) {
105
+ const dir = path.join(nodeModules, scope);
106
+ if (fs.existsSync(dir)) for (const child of fs.readdirSync(dir)) yield `${scope}/${child}`;
107
+ }
108
+ }
109
+ }
110
+ function packageDirOf(file) {
111
+ return /^.*\/node_modules\/(?:@[^/]+\/)?[^/]+(?=\/)/.exec(file)?.[0];
112
+ }
113
+ const name = "vitest-plugin-rsc:next-server-code";
114
+ const asyncFunctionsName = "vitest-plugin-rsc:next-async-functions";
115
+ function nextModulesWithStoresOf(layer) {
116
+ const modules = nextModulesWithStores.filter((entry) => entry.layer === layer);
117
+ if (modules.length === 0) return;
118
+ return withStoresFiles(modules.map(({ module }) => module));
119
+ }
120
+ const performWork = "function performWork(request) {";
121
+ const issues = "https://github.com/storybookjs/storybook-for-pages/issues";
122
+ function nextVersionOf(id) {
123
+ const root = normalizePath(id).match(/^(.*\/next)\/dist\//)?.[1];
124
+ try {
125
+ return JSON.parse(fs.readFileSync(`${root}/package.json`, "utf8")).version;
126
+ } catch {
127
+ return "?";
128
+ }
129
+ }
130
+ function createServerCode(registry, options = {}) {
131
+ let asyncContext = "compiled";
132
+ const patterns = [options.browserModules ?? []].flat();
133
+ const hostFiles = [options.host?.files ?? []].flat();
134
+ const packages = options.host?.packages ?? [];
135
+ let isBrowserModule = () => false;
136
+ const testFileMatchers = [];
137
+ let isOwnFileOfHost = () => false;
138
+ const hostPackageNamesOfOptions = hostPackages(packages);
139
+ let hostPackageDirs = /* @__PURE__ */ new Set();
140
+ /**
141
+ * Whether a file is one of the host's: a test file or a setup file of
142
+ * Vitest, or a file of another host, like a story.
143
+ */
144
+ function isHostFile(file) {
145
+ return isOwnFileOfHost(normalizePath(file)) || isTestFile(file);
146
+ }
147
+ /** Whether a file is a test file or a setup file of Vitest. */
148
+ function isTestFile(file) {
149
+ file = normalizePath(file);
150
+ return testFileMatchers.some((matches) => matches(file));
151
+ }
152
+ const knowsHostFiles = () => hostFiles.length > 0 || testFileMatchers.length > 0;
153
+ /**
154
+ * Whether a file is server code in a layer. The rsc layer shares its
155
+ * environment with the test: the test files, what runs them and the
156
+ * `browserModules` of the options are not.
157
+ */
158
+ function isServerCode(file, layer) {
159
+ if (layer === "browser" || !path.isAbsolute(file)) return false;
160
+ file = normalizePath(file);
161
+ if (isOwnFile(file)) return false;
162
+ if (layer === "ssr") return true;
163
+ if (isBrowserModule(file) || isHostFile(file)) return false;
164
+ return !isHostPackageFile(file);
165
+ }
166
+ /**
167
+ * Whether a file is of a package of the host or of what one depends on:
168
+ * Vitest's runtime, or a package of `host.packages`.
169
+ */
170
+ function isHostPackageFile(file) {
171
+ const packageDir = packageDirOf(normalizePath(file));
172
+ return packageDir !== void 0 && hostPackageDirs.has(packageDir);
173
+ }
174
+ function compileAsyncFunctions(context, code, file) {
175
+ if (asyncContext === "native") return;
176
+ try {
177
+ return transformAsyncFunctions(code, file, `${registry}.asyncFunctionHooks`);
178
+ } catch (error) {
179
+ const reason = String(error instanceof Error ? error.message : error);
180
+ context.warn(`vitest-plugin-rsc: the async functions of ${file} do not keep their stores across an await, it does not parse as JavaScript. ${reason.replace(/\s+/g, " ")}`);
181
+ }
182
+ }
183
+ /** The file of a module, if it is a source file that is server code in a layer. */
184
+ function sourceFileOf(id, cacheDir, layer) {
185
+ const file = id.split("?")[0];
186
+ if (!/\.[cm]?[jt]sx?$/.test(file) || !fs.existsSync(file)) return;
187
+ if (file.includes("/node_modules/") || file.startsWith(`${cacheDir}/`)) return;
188
+ if (isServerCode(file, layer)) return file;
189
+ }
190
+ function flightServerOf(code, id) {
191
+ if (asyncContext === "native" || !vendoredFlightServer.test(normalizePath(id))) return;
192
+ if (!code.includes(performWork)) throw new Error(`vitest-plugin-rsc: ${id}, React's Flight server of next@${nextVersionOf(id)}, has no \`${performWork}\` to compile, which every render of a server needs to keep its stores. This Next.js is newer than the plugin knows: report it at ${issues}.`);
193
+ return {
194
+ code: code.replace(performWork, `function performWork(request) { ${registry}.performWork(request, performWorkOfReact); }\nfunction performWorkOfReact(request) {`),
195
+ define: { queueMicrotask: `${registry}.queueMicrotask` }
196
+ };
197
+ }
198
+ return {
199
+ isServerCode,
200
+ isHostFile,
201
+ isHostPackageFile,
202
+ isTestFile,
203
+ /**
204
+ * Whether an import is of a package of the host, by its name: `vitest`,
205
+ * `vitest/browser`, `@vitest/spy`. Not what such a package depends on.
206
+ */
207
+ isHostPackage(specifier) {
208
+ return hostPackageNamesOfOptions.some((name) => name.endsWith("/*") ? specifier.startsWith(name.slice(0, -1)) : specifier === name || specifier.startsWith(`${name}/`));
209
+ },
210
+ /**
211
+ * Whether a source file is code of the app in a layer: what Next's build
212
+ * compiles. Not in the rsc layer: what is the browser's there, the test
213
+ * files and the `browserModules`. And in no layer a file of the host,
214
+ * which with `"use client"` is a module of the browser layer: it is the
215
+ * host's to compile, as any test file of Vitest, with Vite's own
216
+ * transforms and the host's config, not with Next's.
217
+ */
218
+ isAppCode(file, layer) {
219
+ if (!path.isAbsolute(file) || normalizePath(file).includes("/node_modules/")) return false;
220
+ if (layer !== "rsc") return !isOwnFile(file) && !isHostFile(file);
221
+ return knowsHostFiles() && isServerCode(file, layer);
222
+ },
223
+ /** For the `define` of the optimizer of a server layer, which Vite keys its cache on. */
224
+ get cacheKey() {
225
+ return {
226
+ __vitest_plugin_rsc_browser_modules__: JSON.stringify([
227
+ patterns.map(String),
228
+ hostFiles.map(String),
229
+ packages
230
+ ]),
231
+ __vitest_plugin_rsc_modules_with_stores__: JSON.stringify(nextModulesWithStores),
232
+ __vitest_plugin_rsc_async_context__: JSON.stringify(asyncContext)
233
+ };
234
+ },
235
+ /**
236
+ * The same for the browser layer, which pre-bundles the packages of the
237
+ * host as the page's own: Vite does not key its cache on the plugins of
238
+ * the optimizer.
239
+ */
240
+ browserCacheKey: { __vitest_plugin_rsc_host_packages__: JSON.stringify(packages) },
241
+ /** For a module this plugin generates, with the constants of its layer. */
242
+ compile: async (code, id, define) => (await compileServerCode(code, id, registry, define))?.code ?? code,
243
+ /**
244
+ * Call once the root of the project is known, and where the server runs:
245
+ * see `AsyncContext`.
246
+ */
247
+ configure(root, carried = "compiled") {
248
+ asyncContext = carried;
249
+ if (patterns.length > 0) isBrowserModule = createFilter(patterns, null, { resolve: root });
250
+ if (hostFiles.length > 0) isOwnFileOfHost = createFilter(hostFiles, null, { resolve: root });
251
+ hostPackageDirs = findPackagesWithDependencies(hostPackageNames(root, packages), root);
252
+ },
253
+ /** Call with what a Vitest config says is a test file or a setup file. */
254
+ addTestFiles(matches) {
255
+ testFileMatchers.push(matches);
256
+ },
257
+ /**
258
+ * What a dependency of a server layer needs on top of `compileServerCode()`
259
+ * to keep its stores across an `await` and a render, for a static build,
260
+ * which has no optimizer: see `optimizerDefine()`, `optimizerPlugin()` and
261
+ * `asyncFunctionsOptimizerPlugin()`.
262
+ */
263
+ forStores(context, code, id, layer) {
264
+ if (nextModulesWithStoresOf(layer)?.test(normalizePath(id))) return {
265
+ code: compileAsyncFunctions(context, code, id)?.code ?? code,
266
+ define: {}
267
+ };
268
+ return (layer === "rsc" ? flightServerOf(code, id) : void 0) ?? {
269
+ code,
270
+ define: {}
271
+ };
272
+ },
273
+ /**
274
+ * For the dependency optimizer of a layer: what the bundler replaces in
275
+ * every module of the pre-bundle, by its `transform.define`, natively and
276
+ * in parallel. In the ssr layer every dependency is server code
277
+ * (`isServerCode()`), and of this plugin's own files the pre-bundle has
278
+ * only its stand-ins of the modules of Node.js and of `fastCrypto`: those
279
+ * take the server's timers too, as Node's own modules do. The bundler
280
+ * also reaches a dependency in TypeScript or JSX, which oxc does not take
281
+ * as JavaScript. The rsc layer pre-bundles the host's packages with the
282
+ * app's, so there it is `optimizerPlugin()`, file by file.
283
+ */
284
+ optimizerDefine(layer) {
285
+ return layer === "ssr" ? {
286
+ ...serverGlobalsOf(registry),
287
+ ...browserTypeofs
288
+ } : {};
289
+ },
290
+ /**
291
+ * For the dependency optimizer of the rsc layer: its pre-bundled
292
+ * dependencies that are server code.
293
+ */
294
+ optimizerPlugin() {
295
+ return {
296
+ name,
297
+ async transform(code, id) {
298
+ if (!/\.[cm]?js$/.test(id) || !isServerCode(id, "rsc")) return;
299
+ const flight = flightServerOf(code, id);
300
+ if (flight) code = flight.code;
301
+ const define = flight?.define;
302
+ try {
303
+ return await compileServerCode(code, id, registry, define);
304
+ } catch (error) {
305
+ const reason = String(error instanceof Error ? error.message : error);
306
+ this.warn(`vitest-plugin-rsc: ${id} is not compiled as server code, it does not parse as JavaScript. ${reason.replace(/\s+/g, " ")}`);
307
+ }
308
+ }
309
+ };
310
+ },
311
+ /**
312
+ * The async functions that have to read, after an `await`, a store Next
313
+ * entered for them, or left for them with `exit()`: see
314
+ * async-local-storage-transform.ts. For the dependency optimizer of a
315
+ * server layer, the modules of Next's server in `nextModulesWithStores`
316
+ * that are that layer's. The bundler hands it each file of a dependency
317
+ * it pre-bundles, by its path, so a module inside the bundle of
318
+ * `app-page/module` is one.
319
+ */
320
+ asyncFunctionsOptimizerPlugin(layer) {
321
+ const modules = nextModulesWithStoresOf(layer);
322
+ return {
323
+ name: asyncFunctionsName,
324
+ transform(code, id) {
325
+ if (modules?.test(normalizePath(id))) return compileAsyncFunctions(this, code, id);
326
+ }
327
+ };
328
+ },
329
+ /** And the source files of the app in the `rsc` layer, by the name of its environment. */
330
+ asyncFunctionsPlugin(environment) {
331
+ return {
332
+ name: asyncFunctionsName,
333
+ applyToEnvironment: ({ name }) => name === environment,
334
+ transform(code, id) {
335
+ const file = sourceFileOf(id, normalizePath(this.environment.config.cacheDir), "rsc");
336
+ if (file && knowsHostFiles()) return compileAsyncFunctions(this, code, file);
337
+ }
338
+ };
339
+ },
340
+ /** For the source files of the server layers, by the name of their environment. */
341
+ plugin(environments) {
342
+ return {
343
+ name,
344
+ applyToEnvironment: (environment) => Object.hasOwn(environments, environment.name),
345
+ async transform(code, id) {
346
+ const layer = environments[this.environment.name];
347
+ const file = sourceFileOf(id, normalizePath(this.environment.config.cacheDir), layer);
348
+ if (!file) return;
349
+ if (layer === "rsc" && !knowsHostFiles()) throw new Error(`vitest-plugin-rsc: ${file} is a file of the app, or one of the host's, and nothing says which. Under Vitest the test files and the setup files come from its config, through \`configureVitest\`, which Vitest calls for the plugins of a project, not for one of \`injectTestProjects\`. Another host lists its files in the \`host.files\` option of vitestPluginNext().`);
350
+ return compileServerCode(code, file, registry);
351
+ }
352
+ };
353
+ }
354
+ };
355
+ }
356
+ //#endregion
357
+ export { compileServerCode, createServerCode };
@@ -0,0 +1,37 @@
1
+ import { ServerRequest } from "./registry.js";
2
+ import { MiddlewareContext, MiddlewareResult, ResolveRoutesResult } from "@next/routing";
3
+
4
+ //#region src/server-in-front.d.ts
5
+ type InvokeMiddleware = (context: MiddlewareContext) => Promise<MiddlewareResult>;
6
+ declare function incoming<T extends Pick<ServerRequest, "url" | "headers">>(request: T): T;
7
+ declare function resolve(request: Pick<ServerRequest, "url" | "headers">, requestBody: ReadableStream<Uint8Array>, invokeMiddleware: InvokeMiddleware, proxy: boolean): Promise<ResolveRoutesResult>;
8
+ /**
9
+ * What the middleware did with a request, read off its `response`, with the
10
+ * `headers` and the `url` it got the request with: as `next start` reads it.
11
+ *
12
+ * That is `responseToMiddlewareResult()` of `@next/routing`, but for a
13
+ * response with a `Location` header and a status that is no redirect. Next's
14
+ * own server in front of the app, `resolveRoutes()` of
15
+ * `next/dist/server/lib/router-utils/resolve-routes`, answers with that
16
+ * response, its status and its body: the middleware answered itself. The
17
+ * function of `@next/routing` keeps the header for the response, and says
18
+ * neither a redirect nor `bodySent`, so its `resolveRoutes()` takes the
19
+ * request on to a route. It leaves out such a response where it marks one
20
+ * that answers itself, with `x-middleware-refresh`, as Next does, and only
21
+ * Next's server has the branch that ends the request for it.
22
+ */
23
+ declare function middlewareResult(response: Response, headers: Headers, url: URL): MiddlewareResult;
24
+ declare function redirectStatus(resolved: ResolveRoutesResult): number | undefined;
25
+ /**
26
+ * Whether the server has something for a request: a route of the app, a page
27
+ * or a route handler, the route of the node a test renders, a redirect or a
28
+ * rewrite of `next.config`, or a middleware whose matcher takes it. Only
29
+ * running the middleware tells what it does with the request. At the pathname
30
+ * a test opened without `proxy`, only the routes count.
31
+ *
32
+ * Nothing runs for the answer, and nothing waits for it: not the ssr layer
33
+ * either.
34
+ */
35
+ declare function takesRequest(request: Pick<ServerRequest, "url" | "headers">): Promise<boolean>;
36
+ //#endregion
37
+ export { InvokeMiddleware, incoming, middlewareResult, redirectStatus, resolve, takesRequest };
@@ -0,0 +1,90 @@
1
+ import { openedAt } from "./registry.js";
2
+ import { filterInternalHeaders, fromNodeOutgoingHttpHeaders, splitCookiesString, toNodeOutgoingHttpHeaders } from "./compat/server/headers.js";
3
+ import { resolveRoutes, responseToMiddlewareResult } from "@next/routing";
4
+ import { routing } from "virtual:vitest-plugin-rsc/next-manifest";
5
+ //#region src/server-in-front.ts
6
+ const pathnames = Object.keys(routing.outputs);
7
+ function incoming(request) {
8
+ const headers = toNodeOutgoingHttpHeaders(request.headers);
9
+ filterInternalHeaders(headers);
10
+ const url = new URL(request.url);
11
+ const isHttps = url.protocol === "https:";
12
+ headers["x-forwarded-host"] ??= headers.host ?? url.host;
13
+ headers["x-forwarded-port"] ??= url.port || (isHttps ? "443" : "80");
14
+ headers["x-forwarded-proto"] ??= isHttps ? "https" : "http";
15
+ return {
16
+ ...request,
17
+ headers: fromNodeOutgoingHttpHeaders(headers)
18
+ };
19
+ }
20
+ async function resolve(request, requestBody, invokeMiddleware, proxy) {
21
+ const resolved = await resolveRoutes({
22
+ url: new URL(request.url),
23
+ headers: request.headers,
24
+ requestBody,
25
+ basePath: routing.basePath,
26
+ buildId: routing.buildId,
27
+ pathnames,
28
+ routes: proxy ? routing.routes : routing.appRoutes,
29
+ invokeMiddleware
30
+ });
31
+ const headers = resolved.resolvedHeaders;
32
+ const cookies = headers?.getSetCookie() ?? [];
33
+ if (headers && cookies.length > 0) {
34
+ headers.delete("set-cookie");
35
+ for (const cookie of cookies.flatMap(splitCookiesString)) headers.append("set-cookie", cookie);
36
+ }
37
+ return resolved;
38
+ }
39
+ /**
40
+ * What the middleware did with a request, read off its `response`, with the
41
+ * `headers` and the `url` it got the request with: as `next start` reads it.
42
+ *
43
+ * That is `responseToMiddlewareResult()` of `@next/routing`, but for a
44
+ * response with a `Location` header and a status that is no redirect. Next's
45
+ * own server in front of the app, `resolveRoutes()` of
46
+ * `next/dist/server/lib/router-utils/resolve-routes`, answers with that
47
+ * response, its status and its body: the middleware answered itself. The
48
+ * function of `@next/routing` keeps the header for the response, and says
49
+ * neither a redirect nor `bodySent`, so its `resolveRoutes()` takes the
50
+ * request on to a route. It leaves out such a response where it marks one
51
+ * that answers itself, with `x-middleware-refresh`, as Next does, and only
52
+ * Next's server has the branch that ends the request for it.
53
+ */
54
+ function middlewareResult(response, headers, url) {
55
+ const result = responseToMiddlewareResult(response, headers, url);
56
+ if (result.bodySent || result.redirect || !response.headers.has("location")) return result;
57
+ if (result.rewrite && result.rewrite.origin !== url.origin) return result;
58
+ return {
59
+ ...result,
60
+ bodySent: true
61
+ };
62
+ }
63
+ function redirectStatus(resolved) {
64
+ const status = resolved.redirect?.status ?? resolved.status;
65
+ return status !== void 0 && status >= 300 && status < 400 ? status : void 0;
66
+ }
67
+ /**
68
+ * Whether the server has something for a request: a route of the app, a page
69
+ * or a route handler, the route of the node a test renders, a redirect or a
70
+ * rewrite of `next.config`, or a middleware whose matcher takes it. Only
71
+ * running the middleware tells what it does with the request. At the pathname
72
+ * a test opened without `proxy`, only the routes count.
73
+ *
74
+ * Nothing runs for the answer, and nothing waits for it: not the ssr layer
75
+ * either.
76
+ */
77
+ async function takesRequest(request) {
78
+ const opened = openedAt(new URL(request.url));
79
+ if (opened?.node) return true;
80
+ let matched = false;
81
+ const invokeMiddleware = async () => {
82
+ matched = true;
83
+ return {};
84
+ };
85
+ const proxy = opened?.proxy ?? true;
86
+ const resolved = await resolve(incoming(request), new ReadableStream(), invokeMiddleware, proxy);
87
+ return matched || redirectStatus(resolved) !== void 0 || resolved.externalRewrite !== void 0 || routing.outputs[resolved.resolvedPathname ?? ""] !== void 0;
88
+ }
89
+ //#endregion
90
+ export { incoming, middlewareResult, redirectStatus, resolve, takesRequest };
@@ -0,0 +1,22 @@
1
+ import { ssr_d_exports } from "./ssr.js";
2
+
3
+ //#region src/server-layers.d.ts
4
+ type SsrLayer = typeof ssr_d_exports;
5
+ /**
6
+ * The rsc layer: Server Components, Server Actions, route handlers and the
7
+ * Flight encoder, see rsc.ts. The document's own module graph.
8
+ */
9
+ declare function rscLayer(): Promise<unknown>;
10
+ /**
11
+ * The ssr layer: Next's request handler and HTML renderer, see ssr.ts, with
12
+ * the rsc layer, which a request needs too. It starts to load the first time
13
+ * something asks for it: a request that the server handles, or the entry of
14
+ * the server's API as it evaluates. The server in front of the app needs
15
+ * neither layer: see server-in-front.ts. A build fetches the layer's file
16
+ * when its runner asks for it: see built-layers.ts.
17
+ */
18
+ declare function ssrLayer(): Promise<SsrLayer>;
19
+ /** The ssr layer once it has loaded: until then there is nothing of it to settle or reset. */
20
+ declare function loadedSsrLayer(): SsrLayer | undefined;
21
+ //#endregion
22
+ export { loadedSsrLayer, rscLayer, ssrLayer };