@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.
- 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 -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 };
|
package/dist/client.d.ts
ADDED
|
@@ -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 };
|