@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,186 @@
1
+ import { clientFileKeyExport } from "./client-file-key.js";
2
+ import { movesItsLinks } from "./styles.js";
3
+ import { builtHostModuleUrl, hostModulePrefix } from "@kasperpeulen/vitest-plugin-rsc/internal/host-module";
4
+ import { normalizePath, parseAstAsync } from "vite";
5
+ import path from "node:path";
6
+ import { hasDirective } from "@vitejs/plugin-rsc/transforms";
7
+ import { testFileReferencesPlugin, testFilesPlugin } from "@kasperpeulen/vitest-plugin-rsc/internal/test-file-references";
8
+ //#region src/client-files.ts
9
+ const parsedFile = /\.(?:tsx|ts|mts|jsx|js|mjs)$/;
10
+ const languageOf = (file) => file.endsWith(".tsx") ? "tsx" : file.endsWith("ts") ? "ts" : "jsx";
11
+ const versionQuery = /^\?v=[\w.-]+$/;
12
+ /**
13
+ * The file that a module of Vite is, by its id: the id, without the version
14
+ * a dev server adds for a dependency. Nothing for an id with another query.
15
+ */
16
+ function fileOfModule(id) {
17
+ const query = id.indexOf("?");
18
+ if (query === -1) return id;
19
+ return versionQuery.test(id.slice(query)) ? id.slice(0, query) : void 0;
20
+ }
21
+ /**
22
+ * The client reference of a module of the rsc layer, in Vite RSC's map of
23
+ * them. With a dev server, Vite RSC has a module in `node_modules` there by
24
+ * its file, without the version, unless the page imports it by the name of
25
+ * its package.
26
+ */
27
+ function clientReferenceOfModule(references, id) {
28
+ const reference = references[id];
29
+ if (reference) return reference;
30
+ const file = fileOfModule(id);
31
+ return file === void 0 ? void 0 : references[file];
32
+ }
33
+ /**
34
+ * What the module of the rsc layer for a file with `"use client"` starts
35
+ * with, in front of the reference module, which has none of the code of the
36
+ * file: the export `clientFileKeyExport` with the file's reference key, by
37
+ * which `loadClientModule()` tells the module, see client-file-key.ts. For a
38
+ * file of the host, `load`, also the line that loads the file in the browser
39
+ * layer by that key, see client-graph.ts. With a dev server the key is the
40
+ * URL of the file in the browser layer. In a build it is a hash.
41
+ */
42
+ function clientFileCode({ referenceKey }, load) {
43
+ const key = `export const ${clientFileKeyExport} = ${JSON.stringify(referenceKey)}; `;
44
+ if (!load) return key;
45
+ return `import { loadClientFile as __vitestPluginRscLoadClientFile } from ${JSON.stringify(load.internal)}; ` + key + `await __vitestPluginRscLoadClientFile(${JSON.stringify(referenceKey)}); `;
46
+ }
47
+ /** What `renderServer()` throws in a file with `"use client"`. */
48
+ const renderServerError = "vitest-plugin-rsc: renderServer() renders on the server, and this test file has \"use client\": it is client code, which cannot render server code. Render a Client Component with `render()` of vitest-browser-react or of @testing-library/react, or test a connected component with renderServer() in a test file without \"use client\".";
49
+ /** What the page imports for a module of the host: see ../host-module.ts. */
50
+ function hostModuleCode(target, testingLibrary) {
51
+ if (target === testingLibrary.specifier) return `import * as module from ${JSON.stringify(testingLibrary.file)};\nexport default {\n ...module,\n async renderServer() {\n throw new Error(${JSON.stringify(renderServerError)});\n },\n};\n`;
52
+ return `import * as module from ${JSON.stringify(target)};\nexport default module;\n`;
53
+ }
54
+ /**
55
+ * Three plugins. One that says which modules are Vitest's files, which Vite
56
+ * RSC makes a reference of last. Before Vite RSC: the modules of the page
57
+ * for the browser layer, the error for a file with `"use client"` that mocks
58
+ * a module, before Vite RSC drops the code of the file, and with a dev
59
+ * server the change of the file's module in the rsc layer for a change of
60
+ * what the file imports in the browser layer. After Vite RSC, which made the file a
61
+ * reference: the line in front of that module, with the file's reference
62
+ * key, which for a file of the host loads the file in the browser layer by
63
+ * that key. Not a file without the directive: a test file without it renders
64
+ * with `renderServer()`, in a page with a graph of its own.
65
+ */
66
+ function clientFiles(options) {
67
+ const { rsc, browser } = options.environments;
68
+ const { testingLibrary, clientComponents, internal, isHostFile, isTestFile, isHostPackage } = options;
69
+ const { clientReferenceOf } = options;
70
+ const hostFileOf = (id) => {
71
+ const file = fileOfModule(id);
72
+ if (file === void 0 || !isHostFile(file)) return;
73
+ return file;
74
+ };
75
+ return [
76
+ {
77
+ name: testFilesPlugin,
78
+ api: { isModuleOfTestFile: (id) => {
79
+ const file = fileOfModule(id);
80
+ return file !== void 0 && isTestFile(file);
81
+ } }
82
+ },
83
+ {
84
+ name: "vitest-plugin-rsc:next-client-files",
85
+ enforce: "pre",
86
+ resolveId: {
87
+ order: "pre",
88
+ handler(source, importer) {
89
+ if (source.startsWith(hostModulePrefix)) return source;
90
+ if (this.environment.name !== browser) return;
91
+ const { root } = this.environment.config;
92
+ if (!importer || importer === path.posix.join(normalizePath(root), "index.html")) return;
93
+ const entry = source === testingLibrary.specifier || source === clientComponents;
94
+ if (isHostPackage(source) || entry) return this.environment.mode === "build" ? {
95
+ id: builtHostModuleUrl(source),
96
+ external: "absolute"
97
+ } : hostModulePrefix + source;
98
+ }
99
+ },
100
+ load(id) {
101
+ if (!id.startsWith(hostModulePrefix)) return;
102
+ const target = id.slice(hostModulePrefix.length);
103
+ if (this.environment.name !== rsc) return "export {};";
104
+ return hostModuleCode(target, testingLibrary);
105
+ },
106
+ async transform(code, id) {
107
+ if (this.environment.name !== rsc || !code.includes("use client")) return;
108
+ const file = hostFileOf(id);
109
+ if (!file || !parsedFile.test(file)) return;
110
+ const ast = await parseAstAsync(code, { lang: languageOf(file) });
111
+ if (!hasDirective(ast.body, "use client")) return;
112
+ const mocking = findMocking(ast);
113
+ if (mocking) this.error(`vitest-plugin-rsc: ${file} calls ${mocking}(), which a file with "use client" cannot: it runs in the browser layer, and Vitest's mocker replaces the modules of the rsc layer. Mock in a setup file or a test file without the directive, or pass a mock as a prop.`);
114
+ },
115
+ hotUpdate: {
116
+ order: "pre",
117
+ handler({ file, modules, server }) {
118
+ if (this.environment.name !== rsc) return;
119
+ const changed = server.environments[browser]?.moduleGraph.getModulesByFile(file);
120
+ if (!changed) return;
121
+ const config = this.environment.getTopLevelConfig();
122
+ const updated = new Set(modules);
123
+ for (const hostFile of hostFilesImporting(changed, isHostFile)) for (const node of this.environment.moduleGraph.getModulesByFile(hostFile) ?? []) if (node.id && clientReferenceOf(node.id, config)) updated.add(node);
124
+ if (updated.size > modules.length) return [...updated];
125
+ }
126
+ }
127
+ },
128
+ {
129
+ name: "vitest-plugin-rsc:next-load-client-files",
130
+ enforce: "post",
131
+ configResolved({ plugins }) {
132
+ const names = plugins.map(({ name }) => name);
133
+ if (names.indexOf(testFileReferencesPlugin) > names.indexOf("vitest-plugin-rsc:next-load-client-files")) throw new Error("vitest-plugin-rsc: vitestPluginNext() needs vitestPluginRSC() before it in the plugins.");
134
+ },
135
+ transform(code, id) {
136
+ if (this.environment.name !== rsc) return;
137
+ const config = this.environment.getTopLevelConfig();
138
+ const reference = clientReferenceOf(id, config);
139
+ if (!reference) return;
140
+ return {
141
+ code: clientFileCode(reference, hostFileOf(id) ? { internal } : void 0) + code,
142
+ map: null
143
+ };
144
+ }
145
+ }
146
+ ];
147
+ }
148
+ /**
149
+ * The files of the host among `modules` and the modules that import them in
150
+ * a module graph, also through other modules. Not through a stylesheet whose
151
+ * links move to the version of now (`movesItsLinks()`).
152
+ */
153
+ function hostFilesImporting(modules, isHostFile) {
154
+ const files = /* @__PURE__ */ new Set();
155
+ const seen = new Set(modules);
156
+ for (const node of seen) {
157
+ if (movesItsLinks(node)) continue;
158
+ if (node.file && isHostFile(node.file)) files.add(node.file);
159
+ for (const importer of node.importers) seen.add(importer);
160
+ }
161
+ return files;
162
+ }
163
+ const mockingCall = /^(?:vi|vitest)\.(?:mock|unmock|doMock|doUnmock|hoisted)$/;
164
+ /** The first call of the AST to a function of Vitest that mocks a module, like `vi.mock`. */
165
+ function findMocking(node) {
166
+ if (Array.isArray(node)) {
167
+ for (const child of node) {
168
+ const found = findMocking(child);
169
+ if (found) return found;
170
+ }
171
+ return;
172
+ }
173
+ if (typeof node !== "object" || node === null) return;
174
+ const { type, callee } = node;
175
+ if (type === "CallExpression" && callee?.type === "MemberExpression" && !callee.computed && callee.object?.type === "Identifier" && callee.property?.type === "Identifier") {
176
+ const name = `${callee.object.name}.${callee.property.name}`;
177
+ if (mockingCall.test(name)) return name;
178
+ }
179
+ for (const [key, value] of Object.entries(node)) {
180
+ if (key === "parent" || typeof value !== "object") continue;
181
+ const found = findMocking(value);
182
+ if (found) return found;
183
+ }
184
+ }
185
+ //#endregion
186
+ export { clientFileCode, clientFiles, clientReferenceOfModule, fileOfModule, findMocking, hostModuleCode, renderServerError };
@@ -0,0 +1,94 @@
1
+ import { StylesheetVersions } from "./stylesheet-versions.js";
2
+
3
+ //#region src/client-graph.d.ts
4
+ /**
5
+ * Evaluates a file of the host with `"use client"` in the module graph of
6
+ * the browser layer: the file with the reference key `key`, which with a dev
7
+ * server is its URL in that layer. The module of the rsc layer for the file
8
+ * calls this before its own code runs, as Vitest evaluates what a file
9
+ * imports before it. So a test file with the directive registers its tests
10
+ * with Vitest, which imports the file in the rsc layer, and a setup file
11
+ * with it its hooks.
12
+ *
13
+ * Once for each version of the file, as the browser evaluates a module
14
+ * once: a second import of the file gets the evaluation of the first, or
15
+ * its error. A change of the file, or of a module that it imports, is a new
16
+ * version of it, which evaluates the file again, and so is
17
+ * `forgetClientFiles()`, which the plugin's setup file calls before every
18
+ * test file, as Vitest evaluates a setup file and a test file again for
19
+ * each. What the file imports, once for the graph until it changes, as the
20
+ * browser does for what Vitest evaluates in the tab.
21
+ */
22
+ declare function loadClientFile(key: string): Promise<void>;
23
+ /**
24
+ * The module that the host imported, as the host uses it. For a file of the
25
+ * host with `"use client"`, which the rsc layer has as a client reference
26
+ * module, the file's own exports from the browser layer: the evaluation the
27
+ * graph has when the host first asks for the module, the one that the
28
+ * module's line loaded unless the graph evaluated the file again since. The
29
+ * same object for the same module, every time. Such a module has the export
30
+ * `clientFileKeyExport`, which the plugin put in front of it, and that alone
31
+ * tells it: other exports, like one that a plugin of the host adds, change
32
+ * nothing. Any other module as it is. Links nothing: a host that uses the
33
+ * file links its stylesheets with `linkStylesheetsOf()`.
34
+ *
35
+ * Rejects for the client reference module of a file that is not the host's,
36
+ * which nothing evaluated in the browser layer.
37
+ */
38
+ declare function loadClientModule<T extends object>(module: T): Promise<T>;
39
+ /**
40
+ * Links the stylesheets of files of the host that the host uses, like a
41
+ * framework that renders a component of one, or one that renders as it
42
+ * loads: what each imports, in the browser layer for a file with
43
+ * `"use client"`, in their order, as they are now. The files as for
44
+ * `hostFiles` of `renderServer()`: absolute, or from the root of the
45
+ * project. Resolves once the stylesheets have loaded.
46
+ *
47
+ * The use ends as `signal` aborts, and without one at `cleanup()`, which ends
48
+ * every use: a stylesheet goes once no use holds it. An aborted signal links
49
+ * nothing, and a use whose signal aborts rejects at once with the signal's
50
+ * reason, as `renderServer()` does: also while it asks for the stylesheets,
51
+ * whose answer then links nothing.
52
+ */
53
+ declare function linkStylesheetsOf(hostFiles: string[], {
54
+ signal
55
+ }?: {
56
+ signal?: AbortSignal;
57
+ }): Promise<void>;
58
+ /**
59
+ * Moves the stylesheets that the files of the host linked to the versions of
60
+ * now, after a change of their CSS: see stylesheet-versions.ts.
61
+ */
62
+ declare function moveGraphLinks(versions: StylesheetVersions): void;
63
+ /**
64
+ * Unlinks the stylesheets of the files of the host, which ends every use of
65
+ * them: `cleanup()` calls this, once it has left the page.
66
+ */
67
+ declare function unlinkStylesheets(): void;
68
+ /**
69
+ * Forgets what the files of the host did in the tab: with a dev server the
70
+ * graph evaluates each again the next time it loads, also one that failed,
71
+ * and the stylesheets they linked go. The plugin's setup file calls this
72
+ * before every test file, under Vitest. The graph keeps what the files
73
+ * import, and a module whose line ran before still gets the evaluation it
74
+ * loaded.
75
+ */
76
+ declare function forgetClientFiles(): void;
77
+ /**
78
+ * Whether the graph evaluated `file`, a file of the host, as one with
79
+ * `"use client"` since `forgetClientFiles()`, with a dev server. Under Vitest
80
+ * that tells a test file with the directive, once Vitest has collected it.
81
+ */
82
+ declare function loadedAsClientFile(file: string): boolean;
83
+ /**
84
+ * What a Server Action calls while no page of the app is open. A Server
85
+ * Action that a component of the browser layer calls goes through
86
+ * `callServer()` of Vite RSC, which calls the global `__viteRscCallServer`.
87
+ * A page sets Next's own, which posts to that page (client.tsx). A client
88
+ * file has no page, and the router of a page that was left would never
89
+ * answer. So this one is there from the start, and again once a page is left
90
+ * (`leavePage()` in page.ts).
91
+ */
92
+ declare function callServerWithoutPage(id: string): Promise<never>;
93
+ //#endregion
94
+ export { callServerWithoutPage, forgetClientFiles, linkStylesheetsOf, loadClientFile, loadClientModule, loadedAsClientFile, moveGraphLinks, unlinkStylesheets };
@@ -0,0 +1,266 @@
1
+ import { loadsNothing, registry } from "./registry.js";
2
+ import { builtFileKey, builtHostFileStylesheetsOf, graphStylesheetAttribute, stylesheetOfUrl, stylesheetsChangedEvent, stylesheetsEvent } from "./styles-shared.js";
3
+ import { buildDirectory, builtFiles } from "./built-files.js";
4
+ import { clientFileKeyExport } from "./client-file-key.js";
5
+ import { moveLink, stylesheetVersions } from "./stylesheet-versions.js";
6
+ import { askDevServer, checkFetchedModules, createEnvironmentRunner, environmentModule, forgetHeardVersions, takeChangedModules, versionsOfAnswers } from "@kasperpeulen/vitest-plugin-rsc/internal/utils";
7
+ import { clientReferencesId } from "@kasperpeulen/vitest-plugin-rsc/internal/built-layers";
8
+ import { isHostModule } from "@kasperpeulen/vitest-plugin-rsc/internal/host-module";
9
+ //#region src/client-graph.ts
10
+ const environment = "react_client";
11
+ let graph;
12
+ const linked = /* @__PURE__ */ new Map();
13
+ const keyOf = (href) => stylesheetOfUrl(href) ?? href;
14
+ let cleanups = 0;
15
+ const evaluations = /* @__PURE__ */ new Map();
16
+ const imported = /* @__PURE__ */ new Set();
17
+ const evaluationOf = /* @__PURE__ */ new WeakMap();
18
+ const isDependency = ({ id, file }) => isHostModule(id) || (file ?? id).includes("/node_modules/");
19
+ const browserRequire = (id) => registry.loadBrowserModule?.(id) ?? loadsNothing();
20
+ /**
21
+ * Evaluates a file of the host with `"use client"` in the module graph of
22
+ * the browser layer: the file with the reference key `key`, which with a dev
23
+ * server is its URL in that layer. The module of the rsc layer for the file
24
+ * calls this before its own code runs, as Vitest evaluates what a file
25
+ * imports before it. So a test file with the directive registers its tests
26
+ * with Vitest, which imports the file in the rsc layer, and a setup file
27
+ * with it its hooks.
28
+ *
29
+ * Once for each version of the file, as the browser evaluates a module
30
+ * once: a second import of the file gets the evaluation of the first, or
31
+ * its error. A change of the file, or of a module that it imports, is a new
32
+ * version of it, which evaluates the file again, and so is
33
+ * `forgetClientFiles()`, which the plugin's setup file calls before every
34
+ * test file, as Vitest evaluates a setup file and a test file again for
35
+ * each. What the file imports, once for the graph until it changes, as the
36
+ * browser does for what Vitest evaluates in the tab.
37
+ */
38
+ async function loadClientFile(key) {
39
+ if (buildDirectory) {
40
+ graph ??= createEnvironmentRunner(environment);
41
+ const load = (await graph.import(environmentModule(environment, clientReferencesId))).default[key];
42
+ if (!load) throw new Error(`vitest-plugin-rsc: the build has no client reference "${key}"`);
43
+ registry.browserRequire = browserRequire;
44
+ evaluations.set(key, await load());
45
+ return;
46
+ }
47
+ graph ??= createEnvironmentRunner(environment, { sourcemaps: true });
48
+ const caughtUp = catchingUp.then(() => catchUp(graph));
49
+ catchingUp = caughtUp.catch(() => {});
50
+ await caughtUp;
51
+ registry.browserRequire = browserRequire;
52
+ imported.add(key);
53
+ evaluations.set(key, await graph.import(key));
54
+ }
55
+ let catchingUp = Promise.resolve();
56
+ async function catchUp(graph) {
57
+ forgetHeardVersions();
58
+ await checkFetchedModules();
59
+ const { evaluatedModules } = graph;
60
+ const changed = /* @__PURE__ */ new Set();
61
+ const add = (id) => {
62
+ const node = evaluatedModules.getModuleById(id);
63
+ if (node && !isDependency(node)) changed.add(node);
64
+ };
65
+ for (const id of await takeChangedModules(graph)) add(id);
66
+ for (const node of changed) node.importers.forEach(add);
67
+ for (const node of changed) evaluatedModules.invalidateModule(node);
68
+ }
69
+ /**
70
+ * The module that the host imported, as the host uses it. For a file of the
71
+ * host with `"use client"`, which the rsc layer has as a client reference
72
+ * module, the file's own exports from the browser layer: the evaluation the
73
+ * graph has when the host first asks for the module, the one that the
74
+ * module's line loaded unless the graph evaluated the file again since. The
75
+ * same object for the same module, every time. Such a module has the export
76
+ * `clientFileKeyExport`, which the plugin put in front of it, and that alone
77
+ * tells it: other exports, like one that a plugin of the host adds, change
78
+ * nothing. Any other module as it is. Links nothing: a host that uses the
79
+ * file links its stylesheets with `linkStylesheetsOf()`.
80
+ *
81
+ * Rejects for the client reference module of a file that is not the host's,
82
+ * which nothing evaluated in the browser layer.
83
+ */
84
+ async function loadClientModule(module) {
85
+ const key = Reflect.get(module, clientFileKeyExport);
86
+ if (typeof key !== "string") return module;
87
+ let exports = evaluationOf.get(module);
88
+ if (!exports) {
89
+ exports = evaluations.get(key);
90
+ if (!exports) throw new Error(`vitest-plugin-rsc: ${key} has "use client" and is not a file of the host, so nothing loaded it in the browser layer for loadClientModule(). ${key} is the reference key that Vite RSC gives the file: its URL with a dev server, and in a static build a hash of the file. The files of the host are the ones that host.files of vitestPluginNext() matches.`);
91
+ evaluationOf.set(module, exports);
92
+ }
93
+ return exports;
94
+ }
95
+ /**
96
+ * Links the stylesheets of files of the host that the host uses, like a
97
+ * framework that renders a component of one, or one that renders as it
98
+ * loads: what each imports, in the browser layer for a file with
99
+ * `"use client"`, in their order, as they are now. The files as for
100
+ * `hostFiles` of `renderServer()`: absolute, or from the root of the
101
+ * project. Resolves once the stylesheets have loaded.
102
+ *
103
+ * The use ends as `signal` aborts, and without one at `cleanup()`, which ends
104
+ * every use: a stylesheet goes once no use holds it. An aborted signal links
105
+ * nothing, and a use whose signal aborts rejects at once with the signal's
106
+ * reason, as `renderServer()` does: also while it asks for the stylesheets,
107
+ * whose answer then links nothing.
108
+ */
109
+ async function linkStylesheetsOf(hostFiles, { signal } = {}) {
110
+ signal?.throwIfAborted();
111
+ const cleanupsBefore = cleanups;
112
+ let held = [];
113
+ const use = (async () => {
114
+ const urls = await stylesheetsOfHostFiles(hostFiles);
115
+ if (signal?.aborted || cleanups !== cleanupsBefore) return;
116
+ held = urls.map(hold);
117
+ await Promise.all(held.map((stylesheet) => stylesheet.loaded));
118
+ })();
119
+ if (!signal) return use;
120
+ const ended = new Promise((_, reject) => signal.addEventListener("abort", () => {
121
+ held.forEach(release);
122
+ reject(signal.reason);
123
+ }, { once: true }));
124
+ return Promise.race([use, ended]);
125
+ }
126
+ const hostStylesheets = /* @__PURE__ */ new Map();
127
+ let hostStylesheetsOf;
128
+ async function stylesheetsOfHostFiles(hostFiles) {
129
+ if (hostFiles.length === 0) return [];
130
+ const versions = await versionsOfAnswers();
131
+ if (versions !== hostStylesheetsOf) {
132
+ hostStylesheets.clear();
133
+ hostStylesheetsOf = versions;
134
+ }
135
+ const key = hostFiles.join("\n");
136
+ let answer = hostStylesheets.get(key);
137
+ if (!answer) {
138
+ const asked = stylesheetsOfHostFilesUncached(hostFiles);
139
+ hostStylesheets.set(key, answer = asked);
140
+ asked.catch(() => hostStylesheets.get(key) === asked && hostStylesheets.delete(key));
141
+ }
142
+ return answer;
143
+ }
144
+ async function stylesheetsOfHostFilesUncached(hostFiles) {
145
+ if (!buildDirectory) return askDevServer(stylesheetsEvent, { hostFiles });
146
+ const built = await builtFiles();
147
+ const keys = Object.keys(built.hostFiles);
148
+ const urls = hostFiles.flatMap((file) => {
149
+ const key = builtFileKey(file, keys);
150
+ return key === void 0 ? [] : builtHostFileStylesheetsOf(built, key, buildDirectory);
151
+ });
152
+ return [...new Set(urls)];
153
+ }
154
+ function hold(href) {
155
+ const known = linked.get(keyOf(href));
156
+ if (known) {
157
+ known.uses++;
158
+ if (known.href !== href) move(known, href);
159
+ return known;
160
+ }
161
+ const link = Object.assign(document.createElement("link"), {
162
+ rel: "stylesheet",
163
+ href
164
+ });
165
+ link.setAttribute(graphStylesheetAttribute, "");
166
+ const stylesheet = {
167
+ href,
168
+ link,
169
+ uses: 1,
170
+ ...loading(link)
171
+ };
172
+ linked.set(keyOf(href), stylesheet);
173
+ document.head.append(link);
174
+ return stylesheet;
175
+ }
176
+ function loading(link) {
177
+ let settle;
178
+ return {
179
+ loaded: new Promise((resolve) => {
180
+ settle = () => resolve();
181
+ link.addEventListener("load", settle, { once: true });
182
+ link.addEventListener("error", settle, { once: true });
183
+ }),
184
+ settle
185
+ };
186
+ }
187
+ function move(stylesheet, href) {
188
+ const link = moveLink(stylesheet.link, href);
189
+ stylesheet.href = href;
190
+ if (link === stylesheet.link) return;
191
+ stylesheet.settle();
192
+ stylesheet.previous = stylesheet.link;
193
+ stylesheet.link = link;
194
+ Object.assign(stylesheet, loading(link));
195
+ }
196
+ /**
197
+ * Moves the stylesheets that the files of the host linked to the versions of
198
+ * now, after a change of their CSS: see stylesheet-versions.ts.
199
+ */
200
+ function moveGraphLinks(versions) {
201
+ for (const [key, stylesheet] of linked) {
202
+ const href = versions.get(key);
203
+ if (href !== void 0 && href !== stylesheet.href) move(stylesheet, href);
204
+ }
205
+ }
206
+ import.meta.hot?.on(stylesheetsChangedEvent, (urls) => moveGraphLinks(stylesheetVersions(urls)));
207
+ function release(stylesheet) {
208
+ if (linked.get(keyOf(stylesheet.href)) !== stylesheet) return;
209
+ if (--stylesheet.uses === 0) unlink(stylesheet);
210
+ }
211
+ function unlink(stylesheet) {
212
+ for (const link of [stylesheet.link, stylesheet.previous]) {
213
+ link?.remove();
214
+ link?.removeAttribute(graphStylesheetAttribute);
215
+ }
216
+ linked.delete(keyOf(stylesheet.href));
217
+ stylesheet.settle();
218
+ }
219
+ /**
220
+ * Unlinks the stylesheets of the files of the host, which ends every use of
221
+ * them: `cleanup()` calls this, once it has left the page.
222
+ */
223
+ function unlinkStylesheets() {
224
+ cleanups++;
225
+ for (const stylesheet of linked.values()) unlink(stylesheet);
226
+ }
227
+ /**
228
+ * Forgets what the files of the host did in the tab: with a dev server the
229
+ * graph evaluates each again the next time it loads, also one that failed,
230
+ * and the stylesheets they linked go. The plugin's setup file calls this
231
+ * before every test file, under Vitest. The graph keeps what the files
232
+ * import, and a module whose line ran before still gets the evaluation it
233
+ * loaded.
234
+ */
235
+ function forgetClientFiles() {
236
+ for (const url of imported) {
237
+ const own = graph?.evaluatedModules.getModuleByUrl(url);
238
+ if (own) graph?.evaluatedModules.invalidateModule(own);
239
+ }
240
+ imported.clear();
241
+ unlinkStylesheets();
242
+ }
243
+ /**
244
+ * Whether the graph evaluated `file`, a file of the host, as one with
245
+ * `"use client"` since `forgetClientFiles()`, with a dev server. Under Vitest
246
+ * that tells a test file with the directive, once Vitest has collected it.
247
+ */
248
+ function loadedAsClientFile(file) {
249
+ return [...graph?.evaluatedModules.getModulesByFile(file) ?? []].some((module) => imported.has(module.url));
250
+ }
251
+ /**
252
+ * What a Server Action calls while no page of the app is open. A Server
253
+ * Action that a component of the browser layer calls goes through
254
+ * `callServer()` of Vite RSC, which calls the global `__viteRscCallServer`.
255
+ * A page sets Next's own, which posts to that page (client.tsx). A client
256
+ * file has no page, and the router of a page that was left would never
257
+ * answer. So this one is there from the start, and again once a page is left
258
+ * (`leavePage()` in page.ts).
259
+ */
260
+ async function callServerWithoutPage(id) {
261
+ const name = id.split("#").at(-1);
262
+ throw new Error(`vitest-plugin-rsc: a component called the Server Action ${name}, and no page of the app is open to post it to. A Server Action needs Next's app, which a file with "use client" does not render in, like a test file with the directive: render the component with renderServer(), from a file without "use client".`);
263
+ }
264
+ globalThis.__viteRscCallServer = callServerWithoutPage;
265
+ //#endregion
266
+ export { callServerWithoutPage, forgetClientFiles, linkStylesheetsOf, loadClientFile, loadClientModule, loadedAsClientFile, moveGraphLinks, unlinkStylesheets };
@@ -0,0 +1,10 @@
1
+ //#region src/client-modules.d.ts
2
+ /**
3
+ * Has the layer load its Client Components with a loader of its own.
4
+ * Returns how to forget the loader of the browser layer, which keeps the
5
+ * modules that it loaded: the layer then has none, as before its first
6
+ * page, unless another page's has come since.
7
+ */
8
+ declare function registerModuleLoader(layer: "ssr" | "browser", ready?: Promise<void>): () => void;
9
+ //#endregion
10
+ export { registerModuleLoader };
@@ -0,0 +1,43 @@
1
+ import { registry } from "./registry.js";
2
+ import clientReferences from "virtual:vitest-plugin-rsc/client-references";
3
+ //#region src/client-modules.ts
4
+ function importModule(id) {
5
+ if (!clientReferences) return import(
6
+ /* @vite-ignore */
7
+ id
8
+ );
9
+ const load = clientReferences[id];
10
+ if (!load) return Promise.reject(/* @__PURE__ */ new Error(`vitest-plugin-rsc: the build has no Client Component "${id}"`));
11
+ return load();
12
+ }
13
+ function createModuleLoader(ready) {
14
+ const modules = /* @__PURE__ */ new Map();
15
+ return (id) => {
16
+ id = id.split("$$cache=")[0];
17
+ let loading = modules.get(id);
18
+ if (!loading) {
19
+ const load = () => importModule(id);
20
+ modules.set(id, loading = ready ? ready.then(load) : load());
21
+ }
22
+ return loading;
23
+ };
24
+ }
25
+ /**
26
+ * Has the layer load its Client Components with a loader of its own.
27
+ * Returns how to forget the loader of the browser layer, which keeps the
28
+ * modules that it loaded: the layer then has none, as before its first
29
+ * page, unless another page's has come since.
30
+ */
31
+ function registerModuleLoader(layer, ready) {
32
+ const loader = createModuleLoader(ready);
33
+ if (layer === "ssr") {
34
+ registry.loadSsrModule = loader;
35
+ return () => {};
36
+ }
37
+ registry.loadBrowserModule = loader;
38
+ return () => {
39
+ if (registry.loadBrowserModule === loader) delete registry.loadBrowserModule;
40
+ };
41
+ }
42
+ //#endregion
43
+ export { registerModuleLoader };
@@ -0,0 +1,42 @@
1
+ import { callServer } from "./compat/browser/client.js";
2
+ import { NodeOnPage } from "./node-on-page.js";
3
+
4
+ //#region src/client.d.ts
5
+ declare global {
6
+ var __viteRscCallServer: typeof callServer;
7
+ var __NEXT_HYDRATED_CB: (() => void) | undefined;
8
+ }
9
+ /**
10
+ * Hydrates the document, or with a `container`, the node of a test in it.
11
+ * Resolves once it has, with how to leave the page. Calls `loaded` once Next's
12
+ * client has loaded, before a module of the app has. A page of a node tells
13
+ * `nodeOnPage` what it does with the node: see node-rendered.tsx. The errors
14
+ * that React reports on the root of the app go in `errors`, in order, but
15
+ * for those of Next's control flow.
16
+ *
17
+ * Rejects once `signal` aborts, when the page is left before it has hydrated.
18
+ * What it started is gone by then: the root, and the listeners React added.
19
+ * A root that Next would create after that renders nothing.
20
+ */
21
+ declare function start(loaded: () => void, container: Element | undefined, signal: AbortSignal, nodeOnPage: NodeOnPage | undefined, errors: unknown[], keep?: boolean): Promise<StartedApp>;
22
+ /** How to leave the page that `start()` or `renderAgain()` opened, and its app. */
23
+ type StartedApp = {
24
+ /** Leaves the page. For a kept app, its root stays, with nothing in it. */unmount(): void; /** Leaves the app: its root goes, and the listeners React added for it. */
25
+ leaveApp(): void;
26
+ };
27
+ /**
28
+ * Renders the page in the document into the root of the app that `start()`
29
+ * kept, in place of the page before it: what Next's entry does as the page
30
+ * loads, without the entry, from the Flight payload in the document. The
31
+ * modules of the app stay, and so does its root, so this is no page load:
32
+ * its Flight payload is decoded, the router gets a state of its own from it,
33
+ * and a tree with a key of its own mounts.
34
+ * Resolves as `start()` does, once it has rendered.
35
+ *
36
+ * What the modules of the app keep stays from the page before: Next's caches
37
+ * of its router are emptied, but the state of a module of the app, or of a
38
+ * package, is still there.
39
+ */
40
+ declare function renderAgain(signal: AbortSignal, errors: unknown[], flight?: Uint8Array): Promise<StartedApp>;
41
+ //#endregion
42
+ export { StartedApp, renderAgain, start };