@kasperpeulen/vitest-plugin-nextjs 0.0.0-stage → 0.1.0-alpha.1

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 (256) 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 +39 -0
  64. package/dist/compile.js +175 -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 +330 -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/host-return-shared.d.ts +5 -0
  89. package/dist/host-return-shared.js +5 -0
  90. package/dist/host-return.d.ts +5 -0
  91. package/dist/host-return.js +16 -0
  92. package/dist/index.d.ts +280 -0
  93. package/dist/index.js +281 -0
  94. package/dist/internal.d.ts +2 -0
  95. package/dist/internal.js +3 -0
  96. package/dist/leftovers.d.ts +11 -0
  97. package/dist/leftovers.js +55 -0
  98. package/dist/mode.d.ts +169 -0
  99. package/dist/mode.js +31 -0
  100. package/dist/node/LICENSES.md +3540 -0
  101. package/dist/node/assert/strict.js +29 -0
  102. package/dist/node/assert.js +3044 -0
  103. package/dist/node/buffer.js +1499 -0
  104. package/dist/node/child_process.js +45 -0
  105. package/dist/node/cluster.js +85 -0
  106. package/dist/node/console.js +96 -0
  107. package/dist/node/constants.js +495 -0
  108. package/dist/node/crypto.js +1388 -0
  109. package/dist/node/dgram.js +78 -0
  110. package/dist/node/diagnostics_channel.js +122 -0
  111. package/dist/node/dns/promises.js +129 -0
  112. package/dist/node/dns.js +137 -0
  113. package/dist/node/domain.js +43 -0
  114. package/dist/node/events.js +1155 -0
  115. package/dist/node/fs/promises.js +40 -0
  116. package/dist/node/fs.js +10375 -0
  117. package/dist/node/http.js +326 -0
  118. package/dist/node/http2.js +305 -0
  119. package/dist/node/https.js +57 -0
  120. package/dist/node/inspector/promises.js +66 -0
  121. package/dist/node/inspector.js +76 -0
  122. package/dist/node/module.js +205 -0
  123. package/dist/node/net.js +392 -0
  124. package/dist/node/os.js +259 -0
  125. package/dist/node/path/posix.js +25 -0
  126. package/dist/node/path/win32.js +25 -0
  127. package/dist/node/path.js +384 -0
  128. package/dist/node/perf_hooks.js +313 -0
  129. package/dist/node/process.js +452 -0
  130. package/dist/node/punycode.js +178 -0
  131. package/dist/node/querystring.js +934 -0
  132. package/dist/node/readline/promises.js +71 -0
  133. package/dist/node/readline.js +56 -0
  134. package/dist/node/repl.js +44 -0
  135. package/dist/node/sqlite.js +22 -0
  136. package/dist/node/stream/consumers.js +47 -0
  137. package/dist/node/stream/promises.js +6 -0
  138. package/dist/node/stream/web.js +52 -0
  139. package/dist/node/stream.js +4323 -0
  140. package/dist/node/string_decoder.js +276 -0
  141. package/dist/node/sys.js +6 -0
  142. package/dist/node/timers/promises.js +71 -0
  143. package/dist/node/timers.js +133 -0
  144. package/dist/node/tls.js +122 -0
  145. package/dist/node/trace_events.js +23 -0
  146. package/dist/node/tty.js +69 -0
  147. package/dist/node/url.js +1426 -0
  148. package/dist/node/util/types.js +1456 -0
  149. package/dist/node/util.js +4807 -0
  150. package/dist/node/v8.js +160 -0
  151. package/dist/node/vm.js +88 -0
  152. package/dist/node/wasi.js +16 -0
  153. package/dist/node/worker_threads.js +123 -0
  154. package/dist/node/zlib.js +1396 -0
  155. package/dist/node-builtins.d.ts +40 -0
  156. package/dist/node-builtins.js +74 -0
  157. package/dist/node-on-page.d.ts +46 -0
  158. package/dist/node-on-page.js +101 -0
  159. package/dist/node-platform.d.ts +36 -0
  160. package/dist/node-platform.js +152 -0
  161. package/dist/node-rendered.d.ts +21 -0
  162. package/dist/node-rendered.js +26 -0
  163. package/dist/node-server.d.ts +46 -0
  164. package/dist/node-server.js +193 -0
  165. package/dist/not-found-page.d.ts +19 -0
  166. package/dist/not-found-page.js +74 -0
  167. package/dist/optimizer-cache-key.d.ts +26 -0
  168. package/dist/optimizer-cache-key.js +73 -0
  169. package/dist/own-package.d.ts +1 -0
  170. package/dist/own-package.js +6 -0
  171. package/dist/page-store.d.ts +33 -0
  172. package/dist/page-store.js +80 -0
  173. package/dist/page-timers.d.ts +22 -0
  174. package/dist/page-timers.js +47 -0
  175. package/dist/page.d.ts +112 -0
  176. package/dist/page.js +549 -0
  177. package/dist/paths.d.ts +13 -0
  178. package/dist/paths.js +47 -0
  179. package/dist/plugin.d.ts +23 -0
  180. package/dist/plugin.js +1006 -0
  181. package/dist/prerender-verdict.d.ts +8 -0
  182. package/dist/prerender-verdict.js +67 -0
  183. package/dist/project/context.d.ts +546 -0
  184. package/dist/project/context.js +111 -0
  185. package/dist/project/entries.d.ts +42 -0
  186. package/dist/project/entries.js +189 -0
  187. package/dist/project/env-worker.d.ts +1 -0
  188. package/dist/project/env-worker.js +9 -0
  189. package/dist/project/env.d.ts +36 -0
  190. package/dist/project/env.js +82 -0
  191. package/dist/project/layers.d.ts +31 -0
  192. package/dist/project/layers.js +150 -0
  193. package/dist/project/loaders.d.ts +71 -0
  194. package/dist/project/loaders.js +300 -0
  195. package/dist/project/metadata.d.ts +8 -0
  196. package/dist/project/metadata.js +34 -0
  197. package/dist/project/module-exports.d.ts +29 -0
  198. package/dist/project/module-exports.js +77 -0
  199. package/dist/project/routes.d.ts +19 -0
  200. package/dist/project/routes.js +92 -0
  201. package/dist/project/routing.d.ts +46 -0
  202. package/dist/project/routing.js +280 -0
  203. package/dist/project/transform.d.ts +8 -0
  204. package/dist/project/transform.js +110 -0
  205. package/dist/project/webpack.d.ts +65 -0
  206. package/dist/project/webpack.js +191 -0
  207. package/dist/project.d.ts +269 -0
  208. package/dist/project.js +58 -0
  209. package/dist/registry-global.d.ts +5 -0
  210. package/dist/registry-global.js +5 -0
  211. package/dist/registry.d.ts +241 -0
  212. package/dist/registry.js +30 -0
  213. package/dist/route-chunks.d.ts +13 -0
  214. package/dist/route-chunks.js +32 -0
  215. package/dist/route-lookup.d.ts +34 -0
  216. package/dist/route-lookup.js +207 -0
  217. package/dist/routes-ahead-shared.d.ts +17 -0
  218. package/dist/routes-ahead-shared.js +9 -0
  219. package/dist/routes-ahead.d.ts +10 -0
  220. package/dist/routes-ahead.js +54 -0
  221. package/dist/rsc.d.ts +19 -0
  222. package/dist/rsc.js +171 -0
  223. package/dist/server-action.d.ts +9 -0
  224. package/dist/server-action.js +15 -0
  225. package/dist/server-code.d.ts +176 -0
  226. package/dist/server-code.js +357 -0
  227. package/dist/server-in-front.d.ts +37 -0
  228. package/dist/server-in-front.js +90 -0
  229. package/dist/server-layers.d.ts +22 -0
  230. package/dist/server-layers.js +33 -0
  231. package/dist/setup-browser.d.ts +1 -0
  232. package/dist/setup-browser.js +8 -0
  233. package/dist/setup-node.d.ts +1 -0
  234. package/dist/setup-node.js +6 -0
  235. package/dist/setup.d.ts +8 -0
  236. package/dist/setup.js +62 -0
  237. package/dist/speed-features.d.ts +13 -0
  238. package/dist/speed-features.js +95 -0
  239. package/dist/ssr.d.ts +70 -0
  240. package/dist/ssr.js +480 -0
  241. package/dist/static-build.d.ts +36 -0
  242. package/dist/static-build.js +92 -0
  243. package/dist/storage.d.ts +31 -0
  244. package/dist/storage.js +94 -0
  245. package/dist/styles-shared.d.ts +137 -0
  246. package/dist/styles-shared.js +124 -0
  247. package/dist/styles.d.ts +102 -0
  248. package/dist/styles.js +730 -0
  249. package/dist/stylesheet-versions.d.ts +17 -0
  250. package/dist/stylesheet-versions.js +43 -0
  251. package/dist/tester.html +10 -0
  252. package/dist/use-cache.d.ts +15 -0
  253. package/dist/use-cache.js +93 -0
  254. package/dist/vitest-version.d.ts +6 -0
  255. package/dist/vitest-version.js +12 -0
  256. package/package.json +125 -3
@@ -0,0 +1,111 @@
1
+ import { loadNextBuild } from "../compat/build.js";
2
+ import { pageEnvOf, readTestEnv } from "./env.js";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ //#region src/project/context.ts
7
+ const workingDirectory = process.cwd();
8
+ let directoryQueue = Promise.resolve();
9
+ function inDirectory(directory, load) {
10
+ const result = directoryQueue.then(async () => {
11
+ process.chdir(directory);
12
+ try {
13
+ return await load();
14
+ } finally {
15
+ process.chdir(workingDirectory);
16
+ }
17
+ });
18
+ directoryQueue = result.catch(() => {});
19
+ return result;
20
+ }
21
+ /** The file of a module of Next by its path, which may leave out `.js` or `/index.js`. */
22
+ function moduleFileAt(base) {
23
+ return [
24
+ base,
25
+ `${base}.js`,
26
+ path.join(base, "index.js")
27
+ ].find((candidate) => fs.statSync(candidate, { throwIfNoEntry: false })?.isFile());
28
+ }
29
+ async function openNextProject(root, projectRequire, options) {
30
+ const nextDir = path.dirname(projectRequire.resolve("next/package.json"));
31
+ const { version } = projectRequire("next/package.json");
32
+ const [major = 0, minor = 0] = version.split(".").map(Number);
33
+ if (major < 16 || major === 16 && minor < 4) throw new Error(`@kasperpeulen/vitest-plugin-nextjs needs next@16.4 or later, found next@${version}.`);
34
+ const fail = (what, cause) => {
35
+ throw new Error(`vitest-plugin-rsc: next@${version} differs from the Next.js this plugin was written for: ${what}. Use a version of @kasperpeulen/vitest-plugin-nextjs that supports next@${version}.`, { cause });
36
+ };
37
+ const replace = (code, piece, replacement, where) => {
38
+ const replaced = code.replace(piece, replacement);
39
+ return replaced === code ? fail(`${where} has no \`${piece}\` to replace`) : replaced;
40
+ };
41
+ const { loadConfig, PHASE_PRODUCTION_BUILD, findPagesDir, generateBuildId, ...next } = loadNextBuild(projectRequire, fail);
42
+ let nextEnv;
43
+ try {
44
+ nextEnv = createRequire(path.join(nextDir, "package.json")).resolve("@next/env");
45
+ } catch (error) {
46
+ return fail(`@next/env is not found next to next (${error.message})`, error);
47
+ }
48
+ const readEnv = () => readTestEnv(root, {
49
+ nextEnv,
50
+ envWorker: options.envWorker,
51
+ build: !!options.static
52
+ }).catch((error) => {
53
+ throw new Error(`vitest-plugin-rsc: the env files of ${root} do not load: ${error.message}`, { cause: error });
54
+ });
55
+ const testEnv = await readEnv();
56
+ for (const [name, value] of Object.entries(testEnv.values)) process.env[name] ??= value;
57
+ const loadedConfig = await inDirectory(root, () => loadConfig(PHASE_PRODUCTION_BUILD, root, { silent: true }));
58
+ const config = {
59
+ ...loadedConfig,
60
+ i18n: null,
61
+ experimental: {
62
+ ...loadedConfig.experimental,
63
+ cachedNavigations: false
64
+ },
65
+ ...options.static && (loadedConfig.images?.loader ?? "default") === "default" && !loadedConfig.images?.loaderFile && { images: {
66
+ ...loadedConfig.images,
67
+ unoptimized: true
68
+ } }
69
+ };
70
+ const { appDir } = findPagesDir(root);
71
+ if (!appDir) throw new Error(`vitest-plugin-rsc: no \`app\` directory found in ${root}`);
72
+ const { pageExtensions } = config;
73
+ const distDir = path.join(root, config.distDir);
74
+ await next.swc.loadBindings(config.experimental.useWasmBinary);
75
+ const buildId = await generateBuildId(config.generateBuildId, () => "vitest");
76
+ const buildEnvironment = {
77
+ __NEXT_BUILD_ID: buildId,
78
+ __NEXT_PREVIEW_MODE_ID: "vitest-preview-mode-id",
79
+ __NEXT_PREVIEW_MODE_SIGNING_KEY: "vitest-preview-mode-signing-key",
80
+ __NEXT_PREVIEW_MODE_ENCRYPTION_KEY: "vitest-preview-mode-encryption-key".padEnd(64, "0")
81
+ };
82
+ const previewProps = {
83
+ previewModeId: buildEnvironment.__NEXT_PREVIEW_MODE_ID,
84
+ previewModeSigningKey: buildEnvironment.__NEXT_PREVIEW_MODE_SIGNING_KEY,
85
+ previewModeEncryptionKey: buildEnvironment.__NEXT_PREVIEW_MODE_ENCRYPTION_KEY
86
+ };
87
+ return {
88
+ root,
89
+ /** What the page's `process.env` gets, see env.ts. */
90
+ env: pageEnvOf(testEnv, next.getNextConfigEnv(config)),
91
+ /** The env again, as the env files have it now. */
92
+ loadEnv: async () => pageEnvOf(await readEnv(), next.getNextConfigEnv(config)),
93
+ /** Finds `next`, and what the project has installed next to it. Not the global one. */
94
+ projectRequire,
95
+ nextDir,
96
+ version,
97
+ fail,
98
+ replace,
99
+ config,
100
+ appDir,
101
+ pageExtensions,
102
+ distDir,
103
+ buildId,
104
+ buildEnvironment,
105
+ previewProps,
106
+ /** The build code of the installed `next`, see compat/build.ts. */
107
+ next
108
+ };
109
+ }
110
+ //#endregion
111
+ export { inDirectory, moduleFileAt, openNextProject };
@@ -0,0 +1,42 @@
1
+ import { componentPagePath } from "../styles-shared.js";
2
+ import { AppLoaderOptions } from "../compat/types.js";
3
+ import { RunLoader } from "./loaders.js";
4
+ import { AppRoutes } from "./routes.js";
5
+ import { FoundMiddleware } from "./routing.js";
6
+ import { NextContext } from "./context.js";
7
+ import { NextProject } from "../project.js";
8
+
9
+ //#region src/project/entries.d.ts
10
+ type AppLoaderContext = {
11
+ getOptions(): AppLoaderOptions;
12
+ _module: {
13
+ buildInfo: Record<string, unknown>;
14
+ };
15
+ _compilation: object;
16
+ _compiler: {
17
+ context: string;
18
+ };
19
+ addDependency(file: string): void;
20
+ addMissingDependency(file: string): void;
21
+ addContextDependency(dir: string): void;
22
+ };
23
+ /**
24
+ * What the loader tree of a route names a file of it: its path from the root
25
+ * of the project, with `/`. Next looks up the stylesheets of a segment by that
26
+ * name without its extension (`RouteSegment`), with a dev server and in a
27
+ * build. Not the absolute path that Next's webpack build names it by: a static
28
+ * build has the tree in its files, which are then the same on every machine.
29
+ * Turbopack's names are from a root too, `[project]/app/page.tsx`.
30
+ */
31
+ declare function nameInLoaderTree(root: string, file: string): string;
32
+ declare function routeEntries(context: NextContext, {
33
+ routes,
34
+ appPathsPerRoute,
35
+ strictRouteMatching,
36
+ routeFile
37
+ }: AppRoutes, {
38
+ middleware,
39
+ middlewareFile
40
+ }: FoundMiddleware, builtinBoundaries: Record<string, string>, runLoader: RunLoader): Pick<NextProject, "componentRoutes" | "loadRouteEntry" | "loadMiddlewareEntry">;
41
+ //#endregion
42
+ export { AppLoaderContext, componentPagePath, nameInLoaderTree, routeEntries };
@@ -0,0 +1,189 @@
1
+ import { componentPagePath } from "../styles-shared.js";
2
+ import { appPageRuntime } from "../compat/paths.js";
3
+ import { normalizePath, parseAst } from "vite";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ import querystring from "node:querystring";
7
+ //#region src/project/entries.ts
8
+ const loadOnce = "const __vitest_plugin_rsc_once__ = (load) => {\n let loaded;\n return () => (loaded ??= load().catch((error) => { loaded = undefined; throw error; }));\n};\n";
9
+ const segmentLoader = /\binstrumentModuleGetter\(\(\) => import\((\/\* webpackMode: "eager" \*\/ "(?:[^"\\]|\\.)*")\)\)/g;
10
+ function loadingOnce(code) {
11
+ const once = code.replace(segmentLoader, "instrumentModuleGetter(__vitest_plugin_rsc_once__(() => import($1)))");
12
+ return once === code ? code : loadOnce + once;
13
+ }
14
+ const componentRoot = "(vitest-plugin-rsc)";
15
+ const componentLayouts = "(vitest-plugin-rsc-layouts)";
16
+ const componentPage = `page: [__next_component__, ${JSON.stringify(componentPagePath)}]`;
17
+ const componentImport = `import { loadComponent as __next_component__ } from "@kasperpeulen/vitest-plugin-nextjs/rsc";\n`;
18
+ function stripTurbopackTransitions(code) {
19
+ return code.replace(/\s+with\s*\{\s*['"]turbopack-transition['"]\s*:\s*['"][^'"]*['"]\s*\}/g, "");
20
+ }
21
+ /**
22
+ * What the loader tree of a route names a file of it: its path from the root
23
+ * of the project, with `/`. Next looks up the stylesheets of a segment by that
24
+ * name without its extension (`RouteSegment`), with a dev server and in a
25
+ * build. Not the absolute path that Next's webpack build names it by: a static
26
+ * build has the tree in its files, which are then the same on every machine.
27
+ * Turbopack's names are from a root too, `[project]/app/page.tsx`.
28
+ */
29
+ function nameInLoaderTree(root, file) {
30
+ return normalizePath(path.relative(root, file));
31
+ }
32
+ const isLiteral = (node) => node?.type === "Literal";
33
+ function nameFiles(root, code) {
34
+ const files = /* @__PURE__ */ new Set();
35
+ const specifiers = /* @__PURE__ */ new Set();
36
+ const strings = [];
37
+ const visit = (node) => {
38
+ if (!node || typeof node !== "object") return;
39
+ const { type, source, callee, arguments: args } = node;
40
+ const specifier = type === "CallExpression" && callee?.type === "Identifier" && callee.name === "require" ? args?.[0] : source;
41
+ if (isLiteral(specifier) && typeof specifier.value === "string") {
42
+ specifiers.add(specifier);
43
+ if ((type === "ImportDeclaration" || type === "ImportExpression") && path.isAbsolute(specifier.value)) files.add(specifier.value);
44
+ }
45
+ if (isLiteral(node) && typeof node.value === "string") strings.push(node);
46
+ for (const value of Object.values(node)) if (value && typeof value === "object") visit(value);
47
+ };
48
+ visit(parseAst(code));
49
+ const names = strings.filter((string) => !specifiers.has(string) && files.has(string.value)).sort((a, b) => b.start - a.start);
50
+ for (const { value, start, end } of names) {
51
+ const name = JSON.stringify(nameInLoaderTree(root, value));
52
+ code = code.slice(0, start) + name + code.slice(end);
53
+ }
54
+ return {
55
+ code,
56
+ segments: [...files].map((file) => ({
57
+ file,
58
+ stylesheetsKey: nameInLoaderTree(root, file).replace(/\.[^.]+$/, "")
59
+ }))
60
+ };
61
+ }
62
+ function routeEntries(context, { routes, appPathsPerRoute, strictRouteMatching, routeFile }, { middleware, middlewareFile }, builtinBoundaries, runLoader) {
63
+ const { root, fail, replace, config, appDir, pageExtensions, buildId, next } = context;
64
+ const { getEdgeServerEntry, loadEntrypoint, nextAppLoader, nextMiddlewareLoader, rootPageType } = next;
65
+ const bindPageEntry = (code, where) => {
66
+ code = replace(code, /\b__webpack_require__\b/g, "__next_require__", where);
67
+ if (!code.includes(`"next/dist/build/templates/app-page-runtime"`) && !code.includes(`'next/dist/build/templates/app-page-runtime'`)) fail(`${where} has no \`${appPageRuntime}\``);
68
+ return `import { requireModule as __next_require__ } from "@kasperpeulen/vitest-plugin-nextjs/rsc";\n${code}`;
69
+ };
70
+ async function loadNodeEntry({ page, pathname }) {
71
+ const segment = (name, children, modules = "{}") => `[${JSON.stringify(name)}, ${children}, ${modules}, null]`;
72
+ let tree = `["__PAGE__", {}, { ${componentPage} }]`;
73
+ for (const name of pathname.split("/").filter(Boolean).reverse()) tree = segment(name, `{ children: ${tree} }`);
74
+ const boundaries = Object.entries(builtinBoundaries).map(([name, file]) => `${JSON.stringify(name)}: [() => import(${JSON.stringify(file)}), ${JSON.stringify(file)}]`);
75
+ tree = segment(componentRoot, `{ children: ${tree} }`, `{ ${boundaries.join(", ")} }`);
76
+ let code;
77
+ try {
78
+ code = await loadEntrypoint("app-page", {
79
+ VAR_DEFINITION_PAGE: page,
80
+ VAR_DEFINITION_PATHNAME: pathname
81
+ }, {
82
+ tree,
83
+ __next_app_require__: "__webpack_require__",
84
+ __next_app_load_chunk__: "() => Promise.resolve()"
85
+ });
86
+ } catch (error) {
87
+ return fail(`the app-page template does not take the injections of next-app-loader (${error.message})`, error);
88
+ }
89
+ return componentImport + bindPageEntry(stripTurbopackTransitions(code), "the app-page template");
90
+ }
91
+ const compilation = {};
92
+ const loadAppRouteEntry = async (route) => {
93
+ const watchFiles = /* @__PURE__ */ new Set();
94
+ const context = {
95
+ getOptions: () => ({
96
+ name: `app${route.page}`,
97
+ page: route.page,
98
+ pagePath: route.pagePath,
99
+ appDir,
100
+ appPaths: route.appPaths,
101
+ allNormalizedAppPaths: Object.keys(appPathsPerRoute),
102
+ pageExtensions,
103
+ rootDir: root,
104
+ basePath: config.basePath,
105
+ assetPrefix: config.assetPrefix,
106
+ nextConfigOutput: config.output ?? "",
107
+ preferredRegion: void 0,
108
+ middlewareConfig: Buffer.from("{}").toString("base64"),
109
+ isGlobalNotFoundEnabled: config.experimental.globalNotFound || void 0,
110
+ explicitParallelRouteChildren: config.experimental.explicitParallelRouteChildren || void 0,
111
+ strictRouteMatching,
112
+ isFinalRouteMatcher: strictRouteMatching
113
+ }),
114
+ _module: { buildInfo: {} },
115
+ _compilation: compilation,
116
+ _compiler: { context: root },
117
+ addDependency: (file) => watchFiles.add(file),
118
+ addMissingDependency: (file) => watchFiles.add(file),
119
+ addContextDependency: () => {}
120
+ };
121
+ let code = stripTurbopackTransitions(await nextAppLoader.call(context));
122
+ const where = "the output of next-app-loader";
123
+ if (route.kind === "route") code = replace(code, /(\buserland: \(\)\s*=>\s*)require\(/, "$1import(", where);
124
+ else code = bindPageEntry(code, where);
125
+ const named = nameFiles(root, code);
126
+ const pageFile = route.kind === "page" ? routeFile(route) : void 0;
127
+ if (pageFile && !named.segments.some((segment) => segment.file === pageFile)) fail(`${where} no longer imports the module of a page by its file`);
128
+ return {
129
+ code: loadingOnce(named.code),
130
+ watchFiles: [...watchFiles].filter((file) => fs.existsSync(file)),
131
+ segments: route.kind === "page" ? named.segments : []
132
+ };
133
+ };
134
+ return {
135
+ componentRoutes: [...[.../* @__PURE__ */ new Set(["/", ...routes.map((route) => route.pathname)])].filter((pathname) => !/^\/_(not-found|global-error)$/.test(pathname)).map((pathname) => ({
136
+ kind: "page",
137
+ page: `${pathname === "/" ? "" : pathname}/page`,
138
+ pathname,
139
+ component: `${componentRoot}${pathname}`
140
+ })), ...routes.filter((route) => route.kind === "page" && routeFile(route) && !route.page.includes("/@")).map((route) => ({
141
+ kind: "page",
142
+ page: route.page,
143
+ pathname: route.pathname,
144
+ component: `${componentLayouts}${route.page}`,
145
+ layouts: true
146
+ }))],
147
+ async loadRouteEntry(route) {
148
+ if (!("component" in route)) return loadAppRouteEntry(route);
149
+ if (!route.layouts) return {
150
+ code: await loadNodeEntry(route),
151
+ watchFiles: [],
152
+ segments: []
153
+ };
154
+ const appRoute = routes.find((candidate) => candidate.page === route.page);
155
+ const pageFile = routeFile(appRoute);
156
+ const file = JSON.stringify(nameInLoaderTree(root, pageFile)).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
157
+ const entry = await loadAppRouteEntry(appRoute);
158
+ const code = replace(entry.code, new RegExp(`\\bpage: \\[\\w+, ${file}\\]`), componentPage, "the output of next-app-loader");
159
+ return {
160
+ code: componentImport + code,
161
+ watchFiles: entry.watchFiles,
162
+ segments: entry.segments.filter((segment) => segment.file !== pageFile)
163
+ };
164
+ },
165
+ async loadMiddlewareEntry() {
166
+ if (!middleware || !middlewareFile) return;
167
+ const [loader, options = ""] = getEdgeServerEntry({
168
+ rootDir: root,
169
+ absolutePagePath: middleware.pagePath,
170
+ buildId,
171
+ bundlePath: middleware.page.slice(1),
172
+ config,
173
+ isDev: false,
174
+ isServerComponent: false,
175
+ page: middleware.page,
176
+ pages: {},
177
+ pagesType: rootPageType,
178
+ middleware: middleware.staticInfo.middleware,
179
+ middlewareConfig: middleware.staticInfo.middleware,
180
+ preferredRegion: middleware.staticInfo.preferredRegion
181
+ }).import.replace(/!$/, "").split("?");
182
+ if (loader !== "next-middleware-loader") fail("`getEdgeServerEntry()` no longer loads the middleware with next-middleware-loader");
183
+ const [code] = await runLoader(nextMiddlewareLoader, querystring.parse(options), middlewareFile);
184
+ return replace(stripTurbopackTransitions(code), /\brequire\(/g, "await import(", "the middleware template");
185
+ }
186
+ };
187
+ }
188
+ //#endregion
189
+ export { componentPagePath, nameInLoaderTree, routeEntries };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,9 @@
1
+ import { createRequire } from "node:module";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ //#region src/project/env-worker.ts
4
+ const { nextEnv, root, files } = workerData;
5
+ const { processEnv } = createRequire(import.meta.url)(nextEnv);
6
+ const [, parsed = {}] = processEnv(files, root, console, true);
7
+ parentPort.postMessage(parsed);
8
+ //#endregion
9
+ export {};
@@ -0,0 +1,36 @@
1
+ //#region src/project/env.d.ts
2
+ /** The env files that a run or a build reads, by their path from the root. */
3
+ declare function envFilesOf(build: boolean): string[];
4
+ type TestEnv = {
5
+ /** Each name of the files, with its value. */values: Record<string, string>; /** The files that were read, by their path from the root. */
6
+ files: string[];
7
+ };
8
+ /**
9
+ * The env files of a run or a build, parsed by the project's `@next/env` in
10
+ * `envWorker`, a thread with `NODE_ENV=test` for all of its env: see
11
+ * env-worker.ts.
12
+ */
13
+ declare function readTestEnv(root: string, {
14
+ nextEnv,
15
+ envWorker,
16
+ build
17
+ }: {
18
+ nextEnv: string;
19
+ envWorker: string;
20
+ build: boolean;
21
+ }): Promise<TestEnv>;
22
+ type PageEnv = TestEnv & {
23
+ /** The names that only `env` of `next.config` has. */configNames: string[];
24
+ };
25
+ /**
26
+ * What the page's `process.env` gets: the test env, and `env` of
27
+ * `next.config`, which Next's `getNextConfigEnv()` gives as defines.
28
+ */
29
+ declare function pageEnvOf(testEnv: TestEnv, configDefines: Record<string, unknown>): PageEnv;
30
+ /**
31
+ * What a run or a build says of the env, once: the files it read, and in a
32
+ * build the names it bundles. Never a value.
33
+ */
34
+ declare function describeEnv(env: PageEnv, build: boolean): string;
35
+ //#endregion
36
+ export { PageEnv, TestEnv, describeEnv, envFilesOf, pageEnvOf, readTestEnv };
@@ -0,0 +1,82 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { Worker } from "node:worker_threads";
4
+ //#region src/project/env.ts
5
+ /** Next's env files in its test mode. A name takes the value of the first file that has it. */
6
+ const testEnvFiles = [
7
+ ".env.test.local",
8
+ ".env.test",
9
+ ".env"
10
+ ];
11
+ /** The env files that a run or a build reads, by their path from the root. */
12
+ function envFilesOf(build) {
13
+ return build ? testEnvFiles.filter((file) => !file.endsWith(".local")) : testEnvFiles;
14
+ }
15
+ const isNextsOwn = (name) => name === "NODE_ENV" || name === "NEXT_RUNTIME" || name.startsWith("__NEXT_");
16
+ /**
17
+ * The env files of a run or a build, parsed by the project's `@next/env` in
18
+ * `envWorker`, a thread with `NODE_ENV=test` for all of its env: see
19
+ * env-worker.ts.
20
+ */
21
+ async function readTestEnv(root, { nextEnv, envWorker, build }) {
22
+ const files = envFilesOf(build).flatMap((file) => {
23
+ const stats = fs.statSync(path.join(root, file), { throwIfNoEntry: false });
24
+ return stats?.isFile() || stats?.isFIFO() ? [{
25
+ path: file,
26
+ contents: fs.readFileSync(path.join(root, file), "utf8"),
27
+ env: {}
28
+ }] : [];
29
+ });
30
+ if (files.length === 0) return {
31
+ values: {},
32
+ files: []
33
+ };
34
+ const worker = new Worker(envWorker, {
35
+ env: { NODE_ENV: "test" },
36
+ workerData: {
37
+ nextEnv,
38
+ root,
39
+ files
40
+ }
41
+ });
42
+ const parsed = await new Promise((resolve, reject) => {
43
+ worker.once("message", resolve);
44
+ worker.once("error", reject);
45
+ worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`the thread exited with ${code}`)));
46
+ });
47
+ return {
48
+ values: Object.fromEntries(Object.entries(parsed).filter(([name]) => !isNextsOwn(name))),
49
+ files: files.map((file) => file.path)
50
+ };
51
+ }
52
+ /**
53
+ * What the page's `process.env` gets: the test env, and `env` of
54
+ * `next.config`, which Next's `getNextConfigEnv()` gives as defines.
55
+ */
56
+ function pageEnvOf(testEnv, configDefines) {
57
+ const configEnv = Object.fromEntries(Object.entries(configDefines).map(([key, value]) => [key.slice(12), String(value)]));
58
+ return {
59
+ values: {
60
+ ...configEnv,
61
+ ...testEnv.values
62
+ },
63
+ files: testEnv.files,
64
+ configNames: Object.keys(configEnv).filter((name) => !(name in testEnv.values))
65
+ };
66
+ }
67
+ /**
68
+ * What a run or a build says of the env, once: the files it read, and in a
69
+ * build the names it bundles. Never a value.
70
+ */
71
+ function describeEnv(env, build) {
72
+ const files = env.files.length > 0 ? env.files.join(", ") : `no env file, as there is no ${envFilesOf(build).join(" or ")}`;
73
+ if (!build) return `vitest-plugin-rsc: process.env of the app's server code is from ${files}`;
74
+ const fileNames = Object.keys(env.values).filter((name) => !env.configNames.includes(name));
75
+ return [
76
+ `vitest-plugin-rsc: the build puts into the site the env of ${files}`,
77
+ fileNames.length > 0 && `: ${fileNames.join(", ")}`,
78
+ env.configNames.length > 0 && `; and env of next.config: ${env.configNames.join(", ")}`
79
+ ].filter(Boolean).join("");
80
+ }
81
+ //#endregion
82
+ export { describeEnv, envFilesOf, pageEnvOf, readTestEnv };
@@ -0,0 +1,31 @@
1
+ import { NextBuild } from "../build.js";
2
+ import { NextContext } from "./context.js";
3
+ import { NextLayer } from "../project.js";
4
+
5
+ //#region src/project/layers.d.ts
6
+ /**
7
+ * What Next's build gives each layer: its compile-time constants and its
8
+ * module aliases. And the exports of the Flight codec of the rsc layer.
9
+ */
10
+ declare function layerTables(context: NextContext, build: NextBuild): Promise<{
11
+ defines: {
12
+ rsc: {
13
+ [x: string]: string;
14
+ };
15
+ ssr: {
16
+ [x: string]: string;
17
+ };
18
+ browser: {
19
+ [x: string]: string;
20
+ };
21
+ };
22
+ aliases: {
23
+ rsc: Record<string, string | false>;
24
+ ssr: Record<string, string | false>;
25
+ browser: Record<string, string | false>;
26
+ };
27
+ appAliases: Record<NextLayer, Record<string, string>>;
28
+ flightExports: Record<"server" | "client" | "static", string[]>;
29
+ }>;
30
+ //#endregion
31
+ export { layerTables };
@@ -0,0 +1,150 @@
1
+ import { rscFlightCodec } from "../flight.js";
2
+ import { aliasPaths, nextModulesWithStores, withStoresId } from "../compat/paths.js";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ import { init, parse } from "cjs-module-lexer";
7
+ //#region src/project/layers.ts
8
+ /**
9
+ * What Next's build gives each layer: its compile-time constants and its
10
+ * module aliases. And the exports of the Flight codec of the rsc layer.
11
+ */
12
+ async function layerTables(context, build) {
13
+ const { root, nextDir, projectRequire, fail, config, appDir, pageExtensions, distDir, buildEnvironment, next } = context;
14
+ const { SUPPORTED_NATIVE_MODULES, compilerAliases, getDefineEnv, getNextPublicEnvironmentVariables, getNextConfigEnv, needsExperimentalReact } = next;
15
+ const definesFor = (layer) => {
16
+ const envKeys = new Set(Object.keys({
17
+ ...getNextPublicEnvironmentVariables(),
18
+ ...getNextConfigEnv(config)
19
+ }));
20
+ const defines = getDefineEnv({
21
+ isTurbopack: false,
22
+ config,
23
+ dev: false,
24
+ distDir,
25
+ projectPath: root,
26
+ fetchCacheKeyPrefix: config.experimental.fetchCacheKeyPrefix,
27
+ hasRewrites: false,
28
+ isClient: layer === "browser",
29
+ isEdgeServer: false,
30
+ isNodeServer: layer !== "browser",
31
+ clientRouterFilters: void 0,
32
+ middlewareMatchers: void 0,
33
+ rewrites: {
34
+ beforeFiles: [],
35
+ afterFiles: [],
36
+ fallback: []
37
+ }
38
+ });
39
+ if (layer !== "browser" && defines["process.env.NEXT_RUNTIME"] !== "\"nodejs\"") fail("`getDefineEnv()` does not define `process.env.NEXT_RUNTIME` as `nodejs`");
40
+ if (layer !== "browser") defines["process.env.__NEXT_USE_NODE_STREAMS"] = "false";
41
+ if ("process.env.__NEXT_RELATIVE_PROJECT_DIR" in defines) defines["process.env.__NEXT_RELATIVE_PROJECT_DIR"] = "\"\"";
42
+ return {
43
+ ...layer !== "browser" && Object.fromEntries(Object.entries(buildEnvironment).map(([name, value]) => [`process.env.${name}`, JSON.stringify(value)])),
44
+ ...Object.fromEntries(Object.entries(defines).filter(([key, value]) => typeof value === "string" && !envKeys.has(key)))
45
+ };
46
+ };
47
+ const appAliases = {
48
+ rsc: {},
49
+ ssr: {},
50
+ browser: {}
51
+ };
52
+ const aliasesFor = (layer) => {
53
+ const aliases = { "@opentelemetry/api$": aliasPaths.openTelemetry };
54
+ if (layer === "browser") for (const name of SUPPORTED_NATIVE_MODULES) {
55
+ if (name === "async_hooks") continue;
56
+ aliases[`${name}$`] = aliases[`node:${name}$`] = aliasPaths.polyfill(name);
57
+ }
58
+ else {
59
+ for (const kind of ["app-page", "app-route"]) {
60
+ const file = aliasPaths.routeModule(kind);
61
+ aliases[`${file}.compiled$`] = aliases[`${file}.compiled.js$`] = file;
62
+ }
63
+ for (const channel of ["", "-experimental"]) aliases[`${aliasPaths.reactDomServer(channel)}$`] = aliasPaths.reactDomServerEdge(channel, build);
64
+ }
65
+ const base = compilerAliases.createWebpackAliases({
66
+ distDir,
67
+ isClient: layer === "browser",
68
+ isEdgeServer: layer !== "browser",
69
+ dev: false,
70
+ config,
71
+ pagesDir: void 0,
72
+ appDir,
73
+ dir: root,
74
+ reactProductionProfiling: false
75
+ });
76
+ for (const [key, target] of Object.entries(base)) aliases[key] = Array.isArray(target) ? target.find((candidate) => ["", ...pageExtensions.map((extension) => `.${extension}`)].some((extension) => path.isAbsolute(candidate) && fs.existsSync(candidate + extension))) ?? false : target;
77
+ const channel = needsExperimentalReact(config) ? "-experimental" : "";
78
+ const vendoredReactAliases = (isEdgeServer) => compilerAliases.createVendoredReactAliases(channel, {
79
+ layer: layer === "browser" ? "app-pages-browser" : layer,
80
+ isBrowser: layer === "browser",
81
+ isEdgeServer,
82
+ reactProductionProfiling: false
83
+ });
84
+ Object.assign(aliases, compilerAliases.createServerOnlyClientOnlyAliases(layer === "rsc"), vendoredReactAliases(layer !== "browser"), compilerAliases.createNextApiEsmAliases(), compilerAliases.createAppRouterApiAliases(layer === "rsc"));
85
+ if (layer === "ssr") {
86
+ const nodeTable = vendoredReactAliases(false);
87
+ for (const key of ["react-dom/server$", "react-dom/static$"]) {
88
+ const entry = nodeTable[key];
89
+ if (typeof entry !== "string") return fail(`Next's alias table for Node.js has no \`${key}\``);
90
+ appAliases[layer][key] = entry;
91
+ }
92
+ }
93
+ if (typeof aliases["react-server-dom-webpack/server$"] !== "string") fail("the alias tables have no `react-server-dom-webpack/server$`");
94
+ for (const { module } of nextModulesWithStores) {
95
+ const id = withStoresId(module);
96
+ try {
97
+ projectRequire.resolve(id);
98
+ } catch (error) {
99
+ return fail(`${id} is not there`, error);
100
+ }
101
+ }
102
+ if (layer === "rsc") {
103
+ const contexts = aliasPaths.vendoredContexts;
104
+ let files;
105
+ try {
106
+ files = fs.readdirSync(path.join(nextDir, contexts.slice(5)));
107
+ } catch {
108
+ return fail(`${contexts} is not there`);
109
+ }
110
+ const names = files.filter((file) => file.endsWith(".js") && file !== "entrypoints.js").map((file) => file.slice(0, -3));
111
+ if (names.length === 0) fail(`${contexts} has no contexts`);
112
+ for (const name of names) aliases[`${aliasPaths.sharedRuntime(name)}$`] = `${contexts}/${name}`;
113
+ }
114
+ return aliases;
115
+ };
116
+ const aliases = {
117
+ rsc: aliasesFor("rsc"),
118
+ ssr: aliasesFor("ssr"),
119
+ browser: aliasesFor("browser")
120
+ };
121
+ await init();
122
+ const exportsOf = (file) => {
123
+ const { exports, reexports } = parse(fs.readFileSync(file, "utf8"));
124
+ const requireFrom = createRequire(file);
125
+ return [...exports.filter((name) => name !== "__esModule"), ...reexports.flatMap((reexport) => exportsOf(requireFrom.resolve(reexport)))];
126
+ };
127
+ const flightExports = {};
128
+ for (const entry of Object.keys(rscFlightCodec)) {
129
+ const specifier = `react-server-dom-webpack/${entry}`;
130
+ const target = aliases.rsc[`${specifier}$`];
131
+ if (typeof target !== "string") return fail(`the alias tables have no \`${specifier}$\``);
132
+ try {
133
+ flightExports[entry] = [...new Set(exportsOf(projectRequire.resolve(target)))];
134
+ } catch (error) {
135
+ return fail(`${target} cannot be read (${error.message})`, error);
136
+ }
137
+ }
138
+ return {
139
+ defines: {
140
+ rsc: definesFor("rsc"),
141
+ ssr: definesFor("ssr"),
142
+ browser: definesFor("browser")
143
+ },
144
+ aliases,
145
+ appAliases,
146
+ flightExports
147
+ };
148
+ }
149
+ //#endregion
150
+ export { layerTables };