@kasperpeulen/vitest-plugin-nextjs 0.0.0-stage → 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.
- package/README.md +249 -2
- package/dist/_virtual/_rolldown/runtime.js +14 -0
- package/dist/action-ids.d.ts +18 -0
- package/dist/action-ids.js +44 -0
- package/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +12 -0
- package/dist/app-requests.d.ts +63 -0
- package/dist/app-requests.js +253 -0
- package/dist/browser-context.d.ts +24 -0
- package/dist/browser-context.js +157 -0
- package/dist/build-output.d.ts +68 -0
- package/dist/build-output.js +216 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +39 -0
- package/dist/built-files.d.ts +9 -0
- package/dist/built-files.js +22 -0
- package/dist/cache.d.ts +44 -0
- package/dist/cache.js +99 -0
- package/dist/client-components.d.ts +3 -0
- package/dist/client-components.js +4 -0
- package/dist/client-file-key.d.ts +5 -0
- package/dist/client-file-key.js +5 -0
- package/dist/client-files.d.ts +83 -0
- package/dist/client-files.js +186 -0
- package/dist/client-graph.d.ts +94 -0
- package/dist/client-graph.js +266 -0
- package/dist/client-modules.d.ts +10 -0
- package/dist/client-modules.js +43 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.js +263 -0
- package/dist/commonjs.d.ts +44 -0
- package/dist/commonjs.js +111 -0
- package/dist/compat/browser/client.d.ts +19 -0
- package/dist/compat/browser/client.js +13 -0
- package/dist/compat/build.d.ts +145 -0
- package/dist/compat/build.js +149 -0
- package/dist/compat/checks.d.ts +16 -0
- package/dist/compat/checks.js +148 -0
- package/dist/compat/patches.d.ts +13 -0
- package/dist/compat/patches.js +30 -0
- package/dist/compat/paths.d.ts +74 -0
- package/dist/compat/paths.js +160 -0
- package/dist/compat/rsc/handlers.d.ts +2 -0
- package/dist/compat/rsc/handlers.js +2 -0
- package/dist/compat/rsc/prerender.d.ts +10 -0
- package/dist/compat/rsc/prerender.js +9 -0
- package/dist/compat/server/headers.d.ts +3 -0
- package/dist/compat/server/headers.js +3 -0
- package/dist/compat/ssr/build-output.d.ts +4 -0
- package/dist/compat/ssr/build-output.js +3 -0
- package/dist/compat/ssr/cache.d.ts +6 -0
- package/dist/compat/ssr/cache.js +6 -0
- package/dist/compat/ssr/handler.d.ts +8 -0
- package/dist/compat/ssr/handler.js +8 -0
- package/dist/compat/ssr/node-server.d.ts +5 -0
- package/dist/compat/ssr/node-server.js +4 -0
- package/dist/compat/ssr/render-errors.d.ts +4 -0
- package/dist/compat/ssr/render-errors.js +4 -0
- package/dist/compat/ssr/senders.d.ts +6 -0
- package/dist/compat/ssr/senders.js +6 -0
- package/dist/compat/types.d.ts +8 -0
- package/dist/compat/types.js +1 -0
- package/dist/compile.d.ts +31 -0
- package/dist/compile.js +140 -0
- package/dist/disk.d.ts +16 -0
- package/dist/disk.js +13 -0
- package/dist/document-loads.d.ts +15 -0
- package/dist/document-loads.js +53 -0
- package/dist/document.d.ts +79 -0
- package/dist/document.js +328 -0
- package/dist/downloads.d.ts +15 -0
- package/dist/downloads.js +71 -0
- package/dist/esm-require.d.ts +11 -0
- package/dist/esm-require.js +32 -0
- package/dist/fast-crypto/index.js +1093 -0
- package/dist/fast-crypto.d.ts +20 -0
- package/dist/fast-crypto.js +151 -0
- package/dist/flight.d.ts +19 -0
- package/dist/flight.js +42 -0
- package/dist/form-submission.d.ts +15 -0
- package/dist/form-submission.js +27 -0
- package/dist/from-page.d.ts +5 -0
- package/dist/from-page.js +44 -0
- package/dist/globals.d.ts +1 -0
- package/dist/globals.js +197 -0
- package/dist/host-document.d.ts +26 -0
- package/dist/host-document.js +34 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +281 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +3 -0
- package/dist/leftovers.d.ts +11 -0
- package/dist/leftovers.js +55 -0
- package/dist/mode.d.ts +169 -0
- package/dist/mode.js +31 -0
- package/dist/node/LICENSES.md +3540 -0
- package/dist/node/assert/strict.js +29 -0
- package/dist/node/assert.js +3044 -0
- package/dist/node/buffer.js +1499 -0
- package/dist/node/child_process.js +45 -0
- package/dist/node/cluster.js +85 -0
- package/dist/node/console.js +96 -0
- package/dist/node/constants.js +495 -0
- package/dist/node/crypto.js +1388 -0
- package/dist/node/dgram.js +78 -0
- package/dist/node/diagnostics_channel.js +122 -0
- package/dist/node/dns/promises.js +129 -0
- package/dist/node/dns.js +137 -0
- package/dist/node/domain.js +43 -0
- package/dist/node/events.js +1155 -0
- package/dist/node/fs/promises.js +40 -0
- package/dist/node/fs.js +10375 -0
- package/dist/node/http.js +326 -0
- package/dist/node/http2.js +305 -0
- package/dist/node/https.js +57 -0
- package/dist/node/inspector/promises.js +66 -0
- package/dist/node/inspector.js +76 -0
- package/dist/node/module.js +205 -0
- package/dist/node/net.js +392 -0
- package/dist/node/os.js +259 -0
- package/dist/node/path/posix.js +25 -0
- package/dist/node/path/win32.js +25 -0
- package/dist/node/path.js +384 -0
- package/dist/node/perf_hooks.js +313 -0
- package/dist/node/process.js +452 -0
- package/dist/node/punycode.js +178 -0
- package/dist/node/querystring.js +934 -0
- package/dist/node/readline/promises.js +71 -0
- package/dist/node/readline.js +56 -0
- package/dist/node/repl.js +44 -0
- package/dist/node/sqlite.js +22 -0
- package/dist/node/stream/consumers.js +47 -0
- package/dist/node/stream/promises.js +6 -0
- package/dist/node/stream/web.js +52 -0
- package/dist/node/stream.js +4323 -0
- package/dist/node/string_decoder.js +276 -0
- package/dist/node/sys.js +6 -0
- package/dist/node/timers/promises.js +71 -0
- package/dist/node/timers.js +133 -0
- package/dist/node/tls.js +122 -0
- package/dist/node/trace_events.js +23 -0
- package/dist/node/tty.js +69 -0
- package/dist/node/url.js +1426 -0
- package/dist/node/util/types.js +1456 -0
- package/dist/node/util.js +4807 -0
- package/dist/node/v8.js +160 -0
- package/dist/node/vm.js +88 -0
- package/dist/node/wasi.js +16 -0
- package/dist/node/worker_threads.js +123 -0
- package/dist/node/zlib.js +1396 -0
- package/dist/node-builtins.d.ts +40 -0
- package/dist/node-builtins.js +74 -0
- package/dist/node-on-page.d.ts +46 -0
- package/dist/node-on-page.js +101 -0
- package/dist/node-platform.d.ts +36 -0
- package/dist/node-platform.js +152 -0
- package/dist/node-rendered.d.ts +21 -0
- package/dist/node-rendered.js +26 -0
- package/dist/node-server.d.ts +46 -0
- package/dist/node-server.js +193 -0
- package/dist/optimizer-cache-key.d.ts +26 -0
- package/dist/optimizer-cache-key.js +73 -0
- package/dist/own-package.d.ts +1 -0
- package/dist/own-package.js +6 -0
- package/dist/page-store.d.ts +33 -0
- package/dist/page-store.js +80 -0
- package/dist/page-timers.d.ts +22 -0
- package/dist/page-timers.js +47 -0
- package/dist/page.d.ts +112 -0
- package/dist/page.js +549 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +47 -0
- package/dist/plugin.d.ts +22 -0
- package/dist/plugin.js +995 -0
- package/dist/prerender-verdict.d.ts +8 -0
- package/dist/prerender-verdict.js +67 -0
- package/dist/project/context.d.ts +546 -0
- package/dist/project/context.js +111 -0
- package/dist/project/entries.d.ts +42 -0
- package/dist/project/entries.js +189 -0
- package/dist/project/env-worker.d.ts +1 -0
- package/dist/project/env-worker.js +9 -0
- package/dist/project/env.d.ts +36 -0
- package/dist/project/env.js +82 -0
- package/dist/project/layers.d.ts +31 -0
- package/dist/project/layers.js +150 -0
- package/dist/project/loaders.d.ts +71 -0
- package/dist/project/loaders.js +300 -0
- package/dist/project/metadata.d.ts +8 -0
- package/dist/project/metadata.js +34 -0
- package/dist/project/module-exports.d.ts +29 -0
- package/dist/project/module-exports.js +77 -0
- package/dist/project/routes.d.ts +19 -0
- package/dist/project/routes.js +92 -0
- package/dist/project/routing.d.ts +46 -0
- package/dist/project/routing.js +280 -0
- package/dist/project/transform.d.ts +8 -0
- package/dist/project/transform.js +110 -0
- package/dist/project/webpack.d.ts +46 -0
- package/dist/project/webpack.js +160 -0
- package/dist/project.d.ts +264 -0
- package/dist/project.js +58 -0
- package/dist/registry-global.d.ts +5 -0
- package/dist/registry-global.js +5 -0
- package/dist/registry.d.ts +241 -0
- package/dist/registry.js +30 -0
- package/dist/route-chunks.d.ts +13 -0
- package/dist/route-chunks.js +32 -0
- package/dist/route-lookup.d.ts +34 -0
- package/dist/route-lookup.js +207 -0
- package/dist/routes-ahead-shared.d.ts +17 -0
- package/dist/routes-ahead-shared.js +9 -0
- package/dist/routes-ahead.d.ts +10 -0
- package/dist/routes-ahead.js +54 -0
- package/dist/rsc.d.ts +19 -0
- package/dist/rsc.js +171 -0
- package/dist/server-action.d.ts +9 -0
- package/dist/server-action.js +15 -0
- package/dist/server-code.d.ts +176 -0
- package/dist/server-code.js +357 -0
- package/dist/server-in-front.d.ts +37 -0
- package/dist/server-in-front.js +90 -0
- package/dist/server-layers.d.ts +22 -0
- package/dist/server-layers.js +33 -0
- package/dist/setup-browser.d.ts +1 -0
- package/dist/setup-browser.js +8 -0
- package/dist/setup-node.d.ts +1 -0
- package/dist/setup-node.js +6 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +62 -0
- package/dist/speed-features.d.ts +13 -0
- package/dist/speed-features.js +95 -0
- package/dist/ssr.d.ts +70 -0
- package/dist/ssr.js +480 -0
- package/dist/static-build.d.ts +36 -0
- package/dist/static-build.js +92 -0
- package/dist/storage.d.ts +31 -0
- package/dist/storage.js +94 -0
- package/dist/styles-shared.d.ts +137 -0
- package/dist/styles-shared.js +124 -0
- package/dist/styles.d.ts +102 -0
- package/dist/styles.js +730 -0
- package/dist/stylesheet-versions.d.ts +17 -0
- package/dist/stylesheet-versions.js +43 -0
- package/dist/tester.html +10 -0
- package/dist/use-cache.d.ts +15 -0
- package/dist/use-cache.js +93 -0
- package/dist/vitest-version.d.ts +6 -0
- package/dist/vitest-version.js +12 -0
- package/package.json +125 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { esmExternalRequirePlugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/node-builtins.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* What the server layers import for a module of Node.js: the package's
|
|
6
|
+
* `stand-ins`, for a page in a browser, or Node's own, `native`, for a page in
|
|
7
|
+
* a worker of Node.js. A native module is no file: the layer leaves it to
|
|
8
|
+
* the runner to import, and so does a pre-bundle that imports it.
|
|
9
|
+
*/
|
|
10
|
+
type NodeModules = "stand-ins" | "native";
|
|
11
|
+
/**
|
|
12
|
+
* What a server layer imports for `source`, a module of Node.js with or
|
|
13
|
+
* without `node:`, if it imports another module for it: the file of the
|
|
14
|
+
* package's module, `.../node/fs.js` for `node:fs`, or, `native`, the module
|
|
15
|
+
* of Node.js by its `node:` name.
|
|
16
|
+
*/
|
|
17
|
+
declare function nodeModuleOf(source: string, modules: NodeModules): string | undefined;
|
|
18
|
+
/** Whether `id` is a file of the package's modules of Node.js. */
|
|
19
|
+
declare const isNodeModule: (id: string) => boolean;
|
|
20
|
+
/** Whether `id` is a module of Node.js itself, which `nodeModuleOf()` gives for `native`. */
|
|
21
|
+
declare const isNativeNodeModule: (id: string) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The plugin of a server layer's optimizer for `native` modules: in a
|
|
24
|
+
* pre-bundle, a `require()` of a module of Node.js is an import of it, which
|
|
25
|
+
* the runner imports, and `module.exports` its default export, as Node.js
|
|
26
|
+
* gives it. Rolldown's runtime for a browser has no `require` to call. Also
|
|
27
|
+
* `async_hooks`, which the layer imports as the package's: the import is the
|
|
28
|
+
* layer's to resolve, to Node's own here (../async-hooks-node.ts), like the
|
|
29
|
+
* one of `@sentry/nextjs` for Node.js.
|
|
30
|
+
*/
|
|
31
|
+
declare function nativeRequirePlugin(): ReturnType<typeof esmExternalRequirePlugin>;
|
|
32
|
+
/**
|
|
33
|
+
* What the package of `importer`, a dependency, says it uses for `source` in
|
|
34
|
+
* a browser, by its `browser` field: `false` for nothing, a file of its own
|
|
35
|
+
* as a string, or `undefined` where it says nothing. By the specifier as
|
|
36
|
+
* written: Vite maps `fs` and `node:fs` apart too.
|
|
37
|
+
*/
|
|
38
|
+
declare function browserMapping(source: string, importer: string | undefined): false | string | undefined;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { NodeModules, browserMapping, isNativeNodeModule, isNodeModule, nativeRequirePlugin, nodeModuleOf };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { builtinModules, isBuiltin } from "node:module";
|
|
2
|
+
import { esmExternalRequirePlugin, normalizePath } from "vite";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
//#region src/node-builtins.ts
|
|
7
|
+
const ownFile = fileURLToPath(import.meta.url);
|
|
8
|
+
const nodeDir = normalizePath(path.join(path.dirname(ownFile), path.extname(ownFile) === ".ts" ? "../dist/node" : "./node")) + "/";
|
|
9
|
+
const extension = ".js";
|
|
10
|
+
const notHere = /* @__PURE__ */ new Set(["async_hooks"]);
|
|
11
|
+
const files = /* @__PURE__ */ new Map();
|
|
12
|
+
/**
|
|
13
|
+
* What a server layer imports for `source`, a module of Node.js with or
|
|
14
|
+
* without `node:`, if it imports another module for it: the file of the
|
|
15
|
+
* package's module, `.../node/fs.js` for `node:fs`, or, `native`, the module
|
|
16
|
+
* of Node.js by its `node:` name.
|
|
17
|
+
*/
|
|
18
|
+
function nodeModuleOf(source, modules) {
|
|
19
|
+
if (!isBuiltin(source)) return;
|
|
20
|
+
const name = source.replace(/^node:/, "");
|
|
21
|
+
if (notHere.has(name)) return;
|
|
22
|
+
if (modules === "native") return `node:${name}`;
|
|
23
|
+
if (!files.has(name)) {
|
|
24
|
+
if (!fs.existsSync(nodeDir)) throw new Error(`vitest-plugin-rsc: no ${nodeDir}, the package is not built: run \`pnpm build\``);
|
|
25
|
+
const file = `${nodeDir}${name}${extension}`;
|
|
26
|
+
files.set(name, fs.existsSync(file) ? file : void 0);
|
|
27
|
+
}
|
|
28
|
+
return files.get(name);
|
|
29
|
+
}
|
|
30
|
+
/** Whether `id` is a file of the package's modules of Node.js. */
|
|
31
|
+
const isNodeModule = (id) => id.startsWith(nodeDir);
|
|
32
|
+
/** Whether `id` is a module of Node.js itself, which `nodeModuleOf()` gives for `native`. */
|
|
33
|
+
const isNativeNodeModule = (id) => id.startsWith("node:");
|
|
34
|
+
/**
|
|
35
|
+
* The plugin of a server layer's optimizer for `native` modules: in a
|
|
36
|
+
* pre-bundle, a `require()` of a module of Node.js is an import of it, which
|
|
37
|
+
* the runner imports, and `module.exports` its default export, as Node.js
|
|
38
|
+
* gives it. Rolldown's runtime for a browser has no `require` to call. Also
|
|
39
|
+
* `async_hooks`, which the layer imports as the package's: the import is the
|
|
40
|
+
* layer's to resolve, to Node's own here (../async-hooks-node.ts), like the
|
|
41
|
+
* one of `@sentry/nextjs` for Node.js.
|
|
42
|
+
*/
|
|
43
|
+
function nativeRequirePlugin() {
|
|
44
|
+
const names = builtinModules.filter((name) => !name.startsWith("_") && (notHere.has(name) || nodeModuleOf(name, "native")));
|
|
45
|
+
const external = [.../* @__PURE__ */ new Set([...names, ...names.map((name) => name.replace(/^(?!node:)/, "node:"))])];
|
|
46
|
+
return esmExternalRequirePlugin({ external });
|
|
47
|
+
}
|
|
48
|
+
const browserFields = /* @__PURE__ */ new Map();
|
|
49
|
+
function browserFieldOf(dir) {
|
|
50
|
+
if (browserFields.has(dir)) return browserFields.get(dir);
|
|
51
|
+
let field;
|
|
52
|
+
const file = path.join(dir, "package.json");
|
|
53
|
+
const parent = path.dirname(dir);
|
|
54
|
+
if (fs.existsSync(file)) {
|
|
55
|
+
const { browser } = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
56
|
+
field = typeof browser === "object" && browser ? browser : {};
|
|
57
|
+
} else if (parent !== dir) field = browserFieldOf(parent);
|
|
58
|
+
browserFields.set(dir, field);
|
|
59
|
+
return field;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What the package of `importer`, a dependency, says it uses for `source` in
|
|
63
|
+
* a browser, by its `browser` field: `false` for nothing, a file of its own
|
|
64
|
+
* as a string, or `undefined` where it says nothing. By the specifier as
|
|
65
|
+
* written: Vite maps `fs` and `node:fs` apart too.
|
|
66
|
+
*/
|
|
67
|
+
function browserMapping(source, importer) {
|
|
68
|
+
const file = importer?.split("?")[0];
|
|
69
|
+
if (!file || !path.isAbsolute(file) || !/[\\/]node_modules[\\/]/.test(file)) return;
|
|
70
|
+
const mapping = browserFieldOf(path.dirname(file))?.[source];
|
|
71
|
+
return mapping === false || typeof mapping === "string" ? mapping : void 0;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { browserMapping, isNativeNodeModule, isNodeModule, nativeRequirePlugin, nodeModuleOf };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/node-on-page.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What `NodeRendered` tells the node of its page. A shape, not the class:
|
|
4
|
+
* node-rendered.tsx is an entry of its own in the package, whose types have
|
|
5
|
+
* a copy of the class, and a class with private fields is only itself.
|
|
6
|
+
*/
|
|
7
|
+
type NodeReports = Pick<NodeOnPage, "mounted" | "shown" | "hidden">;
|
|
8
|
+
declare class NodeOnPage {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(pathname: string);
|
|
11
|
+
/**
|
|
12
|
+
* Renders the node again: `swap` puts the node with the next version where
|
|
13
|
+
* the server reads it, and then Next's router asks the server for the page
|
|
14
|
+
* again, one request for every call. Resolves once the page has committed
|
|
15
|
+
* that version or a later one, and run its effects. Rejects at once, and
|
|
16
|
+
* later, when the page no longer has the node.
|
|
17
|
+
*/
|
|
18
|
+
rerender(swap: (version: number) => void): Promise<void>;
|
|
19
|
+
/** The page is at `pathname` now. At another than the node's, the node is not there. */
|
|
20
|
+
at(pathname: string): void;
|
|
21
|
+
/** From the page, in the commit that mounts the node: its router. */
|
|
22
|
+
mounted(refresh: () => void): void;
|
|
23
|
+
/**
|
|
24
|
+
* From the page, from a passive effect, after the effects of the node: it
|
|
25
|
+
* shows the node at `version`.
|
|
26
|
+
*/
|
|
27
|
+
shown(version: number): void;
|
|
28
|
+
/** From the page: the node is no longer shown. Something else may be in its place. */
|
|
29
|
+
hidden(): void;
|
|
30
|
+
/**
|
|
31
|
+
* From the root of the page: React reported an error. For a node that is
|
|
32
|
+
* mounted, a boundary in it shows the error, or one around it does and the
|
|
33
|
+
* node is no longer shown. One that is not mounted by the end of the commit
|
|
34
|
+
* never will be: an error took its place before it hydrated.
|
|
35
|
+
*/
|
|
36
|
+
errored(error: unknown): void;
|
|
37
|
+
/** Something else is in the node's place: a page it redirected to, an error page. */
|
|
38
|
+
replaced(): void;
|
|
39
|
+
/**
|
|
40
|
+
* The page was left: by `cleanup()`, `unmount()`, another `renderServer()`,
|
|
41
|
+
* the app or the dev server.
|
|
42
|
+
*/
|
|
43
|
+
left(by: string): void;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { NodeOnPage, NodeReports };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
//#region src/node-on-page.ts
|
|
2
|
+
var NodeOnPage = class {
|
|
3
|
+
#pathname;
|
|
4
|
+
#version = 0;
|
|
5
|
+
#waiting = /* @__PURE__ */ new Set();
|
|
6
|
+
#shown = 0;
|
|
7
|
+
#refresh;
|
|
8
|
+
#error;
|
|
9
|
+
#gone;
|
|
10
|
+
constructor(pathname) {
|
|
11
|
+
this.#pathname = pathname;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renders the node again: `swap` puts the node with the next version where
|
|
15
|
+
* the server reads it, and then Next's router asks the server for the page
|
|
16
|
+
* again, one request for every call. Resolves once the page has committed
|
|
17
|
+
* that version or a later one, and run its effects. Rejects at once, and
|
|
18
|
+
* later, when the page no longer has the node.
|
|
19
|
+
*/
|
|
20
|
+
rerender(swap) {
|
|
21
|
+
if (this.#gone) return Promise.reject(this.#gone());
|
|
22
|
+
this.#error = void 0;
|
|
23
|
+
const version = ++this.#version;
|
|
24
|
+
swap(version);
|
|
25
|
+
const committed = new Promise((resolve, reject) => {
|
|
26
|
+
this.#waiting.add({
|
|
27
|
+
version,
|
|
28
|
+
resolve,
|
|
29
|
+
reject
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
this.#refresh?.();
|
|
33
|
+
return committed;
|
|
34
|
+
}
|
|
35
|
+
/** The page is at `pathname` now. At another than the node's, the node is not there. */
|
|
36
|
+
at(pathname) {
|
|
37
|
+
if (pathname !== this.#pathname) this.replaced();
|
|
38
|
+
}
|
|
39
|
+
/** From the page, in the commit that mounts the node: its router. */
|
|
40
|
+
mounted(refresh) {
|
|
41
|
+
if (this.#gone) return;
|
|
42
|
+
const first = !this.#refresh;
|
|
43
|
+
this.#refresh = refresh;
|
|
44
|
+
if (first && this.#waiting.size > 0) refresh();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* From the page, from a passive effect, after the effects of the node: it
|
|
48
|
+
* shows the node at `version`.
|
|
49
|
+
*/
|
|
50
|
+
shown(version) {
|
|
51
|
+
this.#shown++;
|
|
52
|
+
if (this.#gone) return;
|
|
53
|
+
this.#error = void 0;
|
|
54
|
+
for (const waiting of this.#waiting) {
|
|
55
|
+
if (waiting.version > version) continue;
|
|
56
|
+
this.#waiting.delete(waiting);
|
|
57
|
+
waiting.resolve();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** From the page: the node is no longer shown. Something else may be in its place. */
|
|
61
|
+
hidden() {
|
|
62
|
+
this.#shown--;
|
|
63
|
+
Promise.resolve().then(() => {
|
|
64
|
+
if (this.#shown === 0) this.replaced();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* From the root of the page: React reported an error. For a node that is
|
|
69
|
+
* mounted, a boundary in it shows the error, or one around it does and the
|
|
70
|
+
* node is no longer shown. One that is not mounted by the end of the commit
|
|
71
|
+
* never will be: an error took its place before it hydrated.
|
|
72
|
+
*/
|
|
73
|
+
errored(error) {
|
|
74
|
+
this.#error = error;
|
|
75
|
+
Promise.resolve().then(() => {
|
|
76
|
+
if (!this.#refresh) this.replaced();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Something else is in the node's place: a page it redirected to, an error page. */
|
|
80
|
+
replaced() {
|
|
81
|
+
if (this.#gone) return;
|
|
82
|
+
const cause = this.#error;
|
|
83
|
+
const error = () => Object.assign(new Error("vitest-plugin-rsc: rerender() renders the node again in its place, and the page has something else there: an error or a not-found page, a page the node redirected to, or a route the app navigated to. Render the node with renderServer() instead.", cause === void 0 ? void 0 : { cause }), { name: "NodeReplacedError" });
|
|
84
|
+
this.#leave(error, error());
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The page was left: by `cleanup()`, `unmount()`, another `renderServer()`,
|
|
88
|
+
* the app or the dev server.
|
|
89
|
+
*/
|
|
90
|
+
left(by) {
|
|
91
|
+
if (this.#gone) return;
|
|
92
|
+
this.#leave(() => /* @__PURE__ */ new Error(`vitest-plugin-rsc: rerender() renders the node again on its page, which ${by} has left. Render the node with renderServer() instead.`), new DOMException(`The page was left before the node had rendered again: by ${by}.`, "AbortError"));
|
|
93
|
+
}
|
|
94
|
+
#leave(gone, reason) {
|
|
95
|
+
this.#gone = gone;
|
|
96
|
+
for (const waiting of this.#waiting) waiting.reject(reason);
|
|
97
|
+
this.#waiting.clear();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
export { NodeOnPage };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
|
|
4
|
+
//#region src/node-platform.d.ts
|
|
5
|
+
/** An export of a module of Next that its stand-in does not have. */
|
|
6
|
+
type NewExport = {
|
|
7
|
+
name: string;
|
|
8
|
+
module: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The stand-ins, for a `registry` in the browser that some of them call and a
|
|
12
|
+
* `prefix` that their module ids start with.
|
|
13
|
+
*/
|
|
14
|
+
declare function createNodePlatform(registry: string, prefix: string): {
|
|
15
|
+
/** The id of the stand-in for a specifier of a server layer, if it has one. */moduleOf(source: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The code of a stand-in, by its id without the prefix, with a stub for
|
|
18
|
+
* each export of Next's module that it has not, of the installed `version`.
|
|
19
|
+
*/
|
|
20
|
+
load(name: string, newExports?: NewExport[], version?: string): string | undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The code of the module of Next that loads `instrumentation.ts`, with a
|
|
24
|
+
* `require` that finds no file. Or why it cannot have one: its one use of
|
|
25
|
+
* `require` has to be a call that loads the file, or this is a Next that
|
|
26
|
+
* loads it otherwise.
|
|
27
|
+
*/
|
|
28
|
+
declare function instrumentationRequire(code: string): MagicString | string;
|
|
29
|
+
/**
|
|
30
|
+
* Next's module that loads `instrumentation.ts`, with a `require` that finds
|
|
31
|
+
* no file: for the dependency optimizer of a server layer, and for a static
|
|
32
|
+
* build, which has no optimizer.
|
|
33
|
+
*/
|
|
34
|
+
declare function instrumentationPlugin(): Plugin;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { NewExport, createNodePlatform, instrumentationPlugin, instrumentationRequire };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { missingHelper, walk } from "./commonjs.js";
|
|
2
|
+
import { instrumentationModule, nodePlatformModuleOf, nodePlatformModules } from "./compat/paths.js";
|
|
3
|
+
import { parseAst } from "vite";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
//#region src/node-platform.ts
|
|
6
|
+
const newExportHelper = `const $$newExport = (message) => {
|
|
7
|
+
const fail = () => { throw new Error(message); };
|
|
8
|
+
return new Proxy(function () {}, {
|
|
9
|
+
apply: fail,
|
|
10
|
+
construct: fail,
|
|
11
|
+
get: (_, key) =>
|
|
12
|
+
typeof key === "symbol" || key === "__esModule" || key === "then" ? undefined : fail(),
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
`;
|
|
16
|
+
function newExportStubs(newExports, version) {
|
|
17
|
+
if (newExports.length === 0) return "";
|
|
18
|
+
return newExportHelper + newExports.map(({ name, module }, index) => {
|
|
19
|
+
const message = `vitest-plugin-rsc: next@${version} has \`${name}\` in ${module}, which the plugin's stand-in for that module does not have. Use a version of @kasperpeulen/vitest-plugin-nextjs that supports next@${version}.`;
|
|
20
|
+
return `const $$new${index} = /* @__PURE__ */ $$newExport(${JSON.stringify(message)});\nexport { $$new${index} as ${JSON.stringify(name)} };\n`;
|
|
21
|
+
}).join("");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The stand-ins, for a `registry` in the browser that some of them call and a
|
|
25
|
+
* `prefix` that their module ids start with.
|
|
26
|
+
*/
|
|
27
|
+
function createNodePlatform(registry, prefix) {
|
|
28
|
+
const forward = (owner, names) => names.map((name) => `export const ${name} = (...args) => ${registry}.${owner}.${name}(...args);`).join("\n");
|
|
29
|
+
const stagedRendering = `throw new Error(${JSON.stringify("vitest-plugin-rsc: Next's staged rendering, for Cache Components, is not supported.")});`;
|
|
30
|
+
const modules = {
|
|
31
|
+
"react-server-node": forward("flightServer", [
|
|
32
|
+
"createTemporaryReferenceSet",
|
|
33
|
+
"decodeReply",
|
|
34
|
+
"decodeReplyFromBusboy",
|
|
35
|
+
"decodeAction",
|
|
36
|
+
"decodeFormState"
|
|
37
|
+
]),
|
|
38
|
+
"load-manifest": forward("node", [
|
|
39
|
+
"loadManifest",
|
|
40
|
+
"evalManifest",
|
|
41
|
+
"loadManifestFromRelativePath",
|
|
42
|
+
"evalManifestFromRelativePath"
|
|
43
|
+
]) + `\nexport const clearManifestCache = () => false;`,
|
|
44
|
+
"node-environment": `
|
|
45
|
+
export const installProcessErrorHandlers = () => {};
|
|
46
|
+
export const isUnhandledRejectionListenerRegistered = () => false;
|
|
47
|
+
export const registerUnhandledRejectionListener = () => {};
|
|
48
|
+
export const setAbortedLogsStyle = () => {};
|
|
49
|
+
`,
|
|
50
|
+
"vendored-react": `
|
|
51
|
+
export const React = undefined;
|
|
52
|
+
export const ReactDOM = undefined;
|
|
53
|
+
export const ReactDOMServer = undefined;
|
|
54
|
+
export const ReactJsxDevRuntime = undefined;
|
|
55
|
+
export const ReactJsxRuntime = undefined;
|
|
56
|
+
export const ReactCompilerRuntime = undefined;
|
|
57
|
+
export const ReactServerDOMTurbopackClient = undefined;
|
|
58
|
+
export const ReactServerDOMTurbopackServer = undefined;
|
|
59
|
+
export const ReactServerDOMTurbopackStatic = undefined;
|
|
60
|
+
export const ReactServerDOMWebpackClient = undefined;
|
|
61
|
+
export const ReactServerDOMWebpackServer = undefined;
|
|
62
|
+
export const ReactServerDOMWebpackStatic = undefined;
|
|
63
|
+
`,
|
|
64
|
+
"gzip-size": `
|
|
65
|
+
export const sync = () => 0;
|
|
66
|
+
export default { sync };
|
|
67
|
+
`,
|
|
68
|
+
"fast-set-immediate": `
|
|
69
|
+
export const unpatchedSetImmediate = (...args) => ${registry}.setImmediate(...args);
|
|
70
|
+
export function DANGEROUSLY_runPendingImmediatesAfterCurrentTask() {
|
|
71
|
+
${stagedRendering}
|
|
72
|
+
}
|
|
73
|
+
export function expectNoPendingImmediates() {}
|
|
74
|
+
export class ImmediateTracker {
|
|
75
|
+
constructor() {
|
|
76
|
+
${stagedRendering}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function runWithNativeImmediateTracking() {
|
|
80
|
+
${stagedRendering}
|
|
81
|
+
}
|
|
82
|
+
`,
|
|
83
|
+
"ssr-app-page-module": `
|
|
84
|
+
export const AppPageRouteModule = new Proxy(class {}, {
|
|
85
|
+
construct: (_, args) => new ${registry}.ssr.AppPageRouteModule(...args),
|
|
86
|
+
});
|
|
87
|
+
export default AppPageRouteModule;
|
|
88
|
+
export const vendored = new Proxy({}, {
|
|
89
|
+
get: (_, key) => ${registry}.ssr.vendored[key],
|
|
90
|
+
});
|
|
91
|
+
${forward("ssr", ["renderToHTMLOrFlight", "prerenderToHTMLOrFlight"])}
|
|
92
|
+
`
|
|
93
|
+
};
|
|
94
|
+
const names = new Map(Object.entries(nodePlatformModules).flatMap(([name, nextModules]) => nextModules.map((nextModule) => [nextModule, name])));
|
|
95
|
+
return {
|
|
96
|
+
/** The id of the stand-in for a specifier of a server layer, if it has one. */
|
|
97
|
+
moduleOf(source) {
|
|
98
|
+
const name = names.get(nodePlatformModuleOf(source) ?? "");
|
|
99
|
+
return name && prefix + name;
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* The code of a stand-in, by its id without the prefix, with a stub for
|
|
103
|
+
* each export of Next's module that it has not, of the installed `version`.
|
|
104
|
+
*/
|
|
105
|
+
load(name, newExports = [], version = "") {
|
|
106
|
+
const code = modules[name];
|
|
107
|
+
return code === void 0 ? void 0 : code + newExportStubs(newExports, version);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const missingRequire = "$$cjsMissing";
|
|
112
|
+
const hookFilename = "INSTRUMENTATION_HOOK_FILENAME";
|
|
113
|
+
/** The files of the module of Next that loads `instrumentation.ts`, of either build. */
|
|
114
|
+
const instrumentationFile = new RegExp(`/next/dist/(?:esm/)?${instrumentationModule.replaceAll(".", "\\.")}\\.js$`);
|
|
115
|
+
/**
|
|
116
|
+
* The code of the module of Next that loads `instrumentation.ts`, with a
|
|
117
|
+
* `require` that finds no file. Or why it cannot have one: its one use of
|
|
118
|
+
* `require` has to be a call that loads the file, or this is a Next that
|
|
119
|
+
* loads it otherwise.
|
|
120
|
+
*/
|
|
121
|
+
function instrumentationRequire(code) {
|
|
122
|
+
const uses = [];
|
|
123
|
+
for (const [node, parent] of walk(parseAst(code))) if (node.type === "Identifier" && node.name === "require") uses.push([node, parent]);
|
|
124
|
+
const [use, call] = uses[0] ?? [];
|
|
125
|
+
if (!(uses.length === 1 && call?.type === "CallExpression" && call.callee === use && [...walk(call)].some(([node]) => node.type === "Identifier" && node.name === hookFilename))) return `\`require\` ${uses.length === 1 ? "once" : `${uses.length} times`}, and not as the one call that loads \`${hookFilename}\``;
|
|
126
|
+
const replaced = new MagicString(code);
|
|
127
|
+
replaced.overwrite(use.start, use.end, missingRequire);
|
|
128
|
+
return replaced.prepend(missingHelper);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Next's module that loads `instrumentation.ts`, with a `require` that finds
|
|
132
|
+
* no file: for the dependency optimizer of a server layer, and for a static
|
|
133
|
+
* build, which has no optimizer.
|
|
134
|
+
*/
|
|
135
|
+
function instrumentationPlugin() {
|
|
136
|
+
return {
|
|
137
|
+
name: "vitest-plugin-rsc:next-instrumentation",
|
|
138
|
+
transform: {
|
|
139
|
+
filter: { id: instrumentationFile },
|
|
140
|
+
handler(code, id) {
|
|
141
|
+
const replaced = instrumentationRequire(code);
|
|
142
|
+
if (typeof replaced === "string") throw new Error(`vitest-plugin-rsc: ${id} has ${replaced}.`);
|
|
143
|
+
return {
|
|
144
|
+
code: replaced.toString(),
|
|
145
|
+
map: replaced.generateMap({ hires: "boundary" })
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { createNodePlatform, instrumentationPlugin, instrumentationRequire };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NodeReports } from "./node-on-page.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/node-rendered.d.ts
|
|
5
|
+
/** @internal For client.tsx, as it starts the page. */
|
|
6
|
+
declare function setNodeOnPage(node: NodeReports | undefined): void;
|
|
7
|
+
/**
|
|
8
|
+
* @internal What the server renders around the node of `renderServer()`, from
|
|
9
|
+
* its first render: it tells `rerender()` the page's router once the node is
|
|
10
|
+
* mounted, which render of the node the page shows, and when the node has
|
|
11
|
+
* left the page. It renders the node and nothing else.
|
|
12
|
+
*/
|
|
13
|
+
declare function NodeRendered({
|
|
14
|
+
version,
|
|
15
|
+
children
|
|
16
|
+
}: {
|
|
17
|
+
version: number;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
}): ReactNode;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { NodeRendered, setNodeOnPage };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRouter } from "next/navigation.js";
|
|
3
|
+
import { useEffect, useLayoutEffect } from "react";
|
|
4
|
+
//#region src/node-rendered.tsx
|
|
5
|
+
let nodeOnPage;
|
|
6
|
+
/** @internal For client.tsx, as it starts the page. */
|
|
7
|
+
function setNodeOnPage(node) {
|
|
8
|
+
nodeOnPage = node;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @internal What the server renders around the node of `renderServer()`, from
|
|
12
|
+
* its first render: it tells `rerender()` the page's router once the node is
|
|
13
|
+
* mounted, which render of the node the page shows, and when the node has
|
|
14
|
+
* left the page. It renders the node and nothing else.
|
|
15
|
+
*/
|
|
16
|
+
function NodeRendered({ version, children }) {
|
|
17
|
+
const { refresh } = useRouter();
|
|
18
|
+
useLayoutEffect(() => nodeOnPage?.mounted(refresh), [refresh]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
nodeOnPage?.shown(version);
|
|
21
|
+
return () => nodeOnPage?.hidden();
|
|
22
|
+
}, [version]);
|
|
23
|
+
return children;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { NodeRendered, setNodeOnPage };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { RequestHandler, ServerRequest } from "./registry.js";
|
|
2
|
+
import { RequestMeta } from "./compat/ssr/node-server.js";
|
|
3
|
+
|
|
4
|
+
//#region src/node-server.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* How the body of a response of `handleRequest()` is written: `written`
|
|
7
|
+
* resolves once it ends, whole or not, and `stop` ends it as a page that is
|
|
8
|
+
* left does. The server writes on whether it is read or not: nothing waits
|
|
9
|
+
* for a reader.
|
|
10
|
+
*/
|
|
11
|
+
type Writing = {
|
|
12
|
+
written: Promise<void>;
|
|
13
|
+
stop: () => void;
|
|
14
|
+
};
|
|
15
|
+
/** The `Writing` of a response of `handleRequest()` with a body. */
|
|
16
|
+
declare const writingOf: (response: Response) => Writing | undefined;
|
|
17
|
+
declare const isPrerenderBailout: (error: unknown) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Counts the bail-outs that Next's handler reports for the prerender that
|
|
20
|
+
* starts now: also those it answers with its error page itself, where its
|
|
21
|
+
* render of a page recovers from the error (`app-page-runtime.js:1133-1224`).
|
|
22
|
+
* The function it returns ends the count and answers it, once: a count that
|
|
23
|
+
* stays would get the bail-outs of the prerenders after it.
|
|
24
|
+
*/
|
|
25
|
+
declare function countBailouts(): () => number;
|
|
26
|
+
/**
|
|
27
|
+
* What the server in front of a route knows of a request that it routed: see
|
|
28
|
+
* `handle()` in ssr.ts.
|
|
29
|
+
*/
|
|
30
|
+
type RoutedRequestMeta = Pick<RequestMeta, "params" | "query">;
|
|
31
|
+
/**
|
|
32
|
+
* One request, by the request handler Next's build makes for its route:
|
|
33
|
+
* `templates/app-page-runtime` for a page and `templates/app-route` for a
|
|
34
|
+
* route handler, as `next start` calls them.
|
|
35
|
+
*/
|
|
36
|
+
declare function handleRequest(request: ServerRequest, context: {
|
|
37
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
38
|
+
}, handler: RequestHandler, routed?: RoutedRequestMeta, {
|
|
39
|
+
statusCode,
|
|
40
|
+
headers
|
|
41
|
+
}?: {
|
|
42
|
+
statusCode?: number;
|
|
43
|
+
headers?: Headers;
|
|
44
|
+
}): Promise<Response>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { RoutedRequestMeta, Writing, countBailouts, handleRequest, isPrerenderBailout, writingOf };
|