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