@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
package/dist/styles.js
ADDED
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { builtFilesPath, componentPagePath, cssDirectory, cssVersionPattern, stylesheetsChangedEvent, stylesheetsEvent } from "./styles-shared.js";
|
|
2
|
+
import { fontRequestOf } from "./compile.js";
|
|
3
|
+
import { routeChunksOf } from "./route-chunks.js";
|
|
4
|
+
import { clientReferenceOfModule } from "./client-files.js";
|
|
5
|
+
import { isBuiltin } from "node:module";
|
|
6
|
+
import { clientReferencesId } from "@kasperpeulen/vitest-plugin-rsc/internal/built-layers";
|
|
7
|
+
import { isCSSRequest, normalizePath, parseAst, send, version } from "vite";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import { getPluginApi } from "@vitejs/plugin-rsc/plugin";
|
|
12
|
+
import { cssBuildDirPlaceholder, toBuildDir } from "@kasperpeulen/vitest-plugin-rsc/internal/static-build";
|
|
13
|
+
import { answerPage } from "@kasperpeulen/vitest-plugin-rsc/internal/page-bridge";
|
|
14
|
+
//#region src/styles.ts
|
|
15
|
+
const linkedQuery = "next-linked";
|
|
16
|
+
const linkedRE = new RegExp(`[?&]${linkedQuery}\\b`);
|
|
17
|
+
const directRE = /[?&]direct\b/;
|
|
18
|
+
const transformOnly = "transform-only";
|
|
19
|
+
/** The id of a stylesheet that Next links, of the id of its file, in a build or not. */
|
|
20
|
+
const linked = (id, build = false) => `${id}${id.includes("?") ? "&" : "?"}${linkedQuery}${build ? `&${transformOnly}` : ""}`;
|
|
21
|
+
const isLinked = (id) => linkedRE.test(id);
|
|
22
|
+
const metaKey = "vitest-plugin-rsc:next-linked";
|
|
23
|
+
/**
|
|
24
|
+
* Whether a module is that of a stylesheet that Next links, whose links the
|
|
25
|
+
* page moves to the version of now after a change
|
|
26
|
+
* (`stylesheetsChangedEvent`). One without class names, which its module
|
|
27
|
+
* exports as a CSS module does, and so exports nothing. Not with
|
|
28
|
+
* `experimental.inlineCss`, with which Next puts the CSS of a page load in a
|
|
29
|
+
* `<style>`, which is no link: the page loads again for it.
|
|
30
|
+
*/
|
|
31
|
+
const movesItsLinks = (node) => (node.info?.meta)?.[metaKey]?.movesLinks === true;
|
|
32
|
+
/**
|
|
33
|
+
* Whether a stylesheet changes in place after a change, and what imports it
|
|
34
|
+
* sees nothing of it: one that accepts its own update, which Vite swaps as
|
|
35
|
+
* its `<style>`, and the module of a stylesheet that Next links without
|
|
36
|
+
* class names, whose links the page moves to the version of now
|
|
37
|
+
* (`stylesheetsChangedEvent`).
|
|
38
|
+
*/
|
|
39
|
+
const changesInPlace = (node) => isCSSRequest(node.url) && (node.isSelfAccepting === true || movesItsLinks(node));
|
|
40
|
+
const isScriptRequest = (source) => /\.[cm]?[jt]sx?$/.test(source);
|
|
41
|
+
const codeExtensionRE = /^(?:[cm]?[jt]sx?|vue|marko|svelte|astro|imba|mdx)$/;
|
|
42
|
+
function isCode(id, pageExtensions) {
|
|
43
|
+
const file = id.split("?")[0];
|
|
44
|
+
const extension = /\.([^./]+)$/.exec(file)?.[1];
|
|
45
|
+
return extension === void 0 || codeExtensionRE.test(extension) || pageExtensions.some((pageExtension) => file.endsWith(`.${pageExtension}`));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The order of the cascade layers of a stylesheet, kept where Vite inlines
|
|
49
|
+
* its `@import`s. A layer comes in the order in which a stylesheet names it
|
|
50
|
+
* first, and an `@import` with `layer()` names it before an `@layer`
|
|
51
|
+
* statement that follows the import. `postcss-import`, with which Vite
|
|
52
|
+
* inlines an import, puts such a statement before the CSS of the import, so
|
|
53
|
+
* that its layers come first. With a statement of every layer of the imports
|
|
54
|
+
* at the top, which can precede an `@import`, the order is the stylesheet's
|
|
55
|
+
* own. Not that of a stylesheet it imports, which `postcss-import` reads
|
|
56
|
+
* itself, nor of a layer without a name, which a statement cannot name.
|
|
57
|
+
*/
|
|
58
|
+
function declareImportedLayers(css) {
|
|
59
|
+
const layers = [];
|
|
60
|
+
let start = 0;
|
|
61
|
+
let importsLayer = false;
|
|
62
|
+
let reordered = false;
|
|
63
|
+
for (let at = 0;;) {
|
|
64
|
+
const space = /^(?:\s+|\/\*[\s\S]*?\*\/)*/.exec(css.slice(at))[0];
|
|
65
|
+
at += space.length;
|
|
66
|
+
const rule = /^@(charset|import|layer)\b((?:"[^"]*"|'[^']*'|\([^)]*\)|[^;{"'(])*);/i.exec(css.slice(at));
|
|
67
|
+
if (!rule) break;
|
|
68
|
+
const [statement, name, prelude] = rule;
|
|
69
|
+
at += statement.length;
|
|
70
|
+
if (name.toLowerCase() === "charset") start = at;
|
|
71
|
+
else if (name.toLowerCase() === "import") {
|
|
72
|
+
const layer = /\blayer\(\s*([^)\s]+)\s*\)/i.exec(prelude.replace(/"[^"]*"|'[^']*'/g, ""));
|
|
73
|
+
if (layer && !layers.includes(layer[1])) layers.push(layer[1]);
|
|
74
|
+
importsLayer ||= Boolean(layer);
|
|
75
|
+
} else {
|
|
76
|
+
reordered ||= importsLayer;
|
|
77
|
+
for (const layer of prelude.split(",").map((part) => part.trim())) if (layer && !layers.includes(layer)) layers.push(layer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!reordered) return css;
|
|
81
|
+
return `${css.slice(0, start)}@layer ${layers.join(", ")};${css.slice(start)}`;
|
|
82
|
+
}
|
|
83
|
+
const directory = cssDirectory;
|
|
84
|
+
const versionRE = new RegExp(`^${directory}/(${cssVersionPattern})(/.*)$`);
|
|
85
|
+
function boundNames(node, names) {
|
|
86
|
+
if (!node || typeof node !== "object") return;
|
|
87
|
+
const { type } = node;
|
|
88
|
+
if (type === "Identifier") names.add(node.name);
|
|
89
|
+
else if (type === "ImportDeclaration") for (const { local } of node.specifiers) boundNames(local, names);
|
|
90
|
+
else if (type === "VariableDeclaration") for (const { id } of node.declarations) boundNames(id, names);
|
|
91
|
+
else if (type === "FunctionDeclaration" || type === "ClassDeclaration") boundNames(node.id, names);
|
|
92
|
+
else for (const key of [
|
|
93
|
+
"properties",
|
|
94
|
+
"elements",
|
|
95
|
+
"value",
|
|
96
|
+
"argument",
|
|
97
|
+
"left"
|
|
98
|
+
]) for (const part of [node[key]].flat()) boundNames(part, names);
|
|
99
|
+
}
|
|
100
|
+
function identifiersOf(node, names) {
|
|
101
|
+
if (!node || typeof node !== "object") return false;
|
|
102
|
+
const { type } = node;
|
|
103
|
+
if (type === "Identifier") names.add(node.name);
|
|
104
|
+
let meta = type === "MetaProperty";
|
|
105
|
+
for (const value of Object.values(node)) if (value && typeof value === "object") meta = identifiersOf(value, names) || meta;
|
|
106
|
+
return meta;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The exports of Vite's module for a stylesheet, without the code that puts
|
|
110
|
+
* the CSS in the document.
|
|
111
|
+
*
|
|
112
|
+
* Vite's module for a client (`vite:css-post`) puts the CSS in a `<style>`,
|
|
113
|
+
* and has the class names of a CSS module as its exports, as Vite documents
|
|
114
|
+
* them. Its module for a server is those exports, with the constants they
|
|
115
|
+
* are made of: one for a class name that is no name of a variable, like
|
|
116
|
+
* `switch`. So the exports are kept, with the statements they use, and the
|
|
117
|
+
* rest is not. That holds as long as what they use is not the code of the
|
|
118
|
+
* `<style>`: an import, or `import.meta`.
|
|
119
|
+
*/
|
|
120
|
+
function exportsOfStylesheet(code, id) {
|
|
121
|
+
const body = parseAst(code).body;
|
|
122
|
+
const isExport = (node) => node.type === "ExportNamedDeclaration" && !node.source || node.type === "ExportDefaultDeclaration";
|
|
123
|
+
const declaredBy = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const node of body) {
|
|
125
|
+
if (isExport(node)) continue;
|
|
126
|
+
const names = /* @__PURE__ */ new Set();
|
|
127
|
+
boundNames(node, names);
|
|
128
|
+
for (const name of names) declaredBy.set(name, node);
|
|
129
|
+
}
|
|
130
|
+
let other;
|
|
131
|
+
const kept = new Set(body.filter(isExport));
|
|
132
|
+
for (const node of kept) {
|
|
133
|
+
const used = /* @__PURE__ */ new Set();
|
|
134
|
+
if (identifiersOf(node, used)) other = code.slice(node.start, node.end);
|
|
135
|
+
for (const name of used) {
|
|
136
|
+
const declaration = declaredBy.get(name);
|
|
137
|
+
if (!declaration || kept.has(declaration)) continue;
|
|
138
|
+
if (declaration.type === "ImportDeclaration") other = name;
|
|
139
|
+
else kept.add(declaration);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const node of body) if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
|
|
143
|
+
if (!kept.has(node)) other = code.slice(node.start, node.end);
|
|
144
|
+
}
|
|
145
|
+
if (other !== void 0) throw new Error(`vitest-plugin-rsc: vite@${version} differs from the Vite this plugin was written for: the exports of its module for the stylesheet ${id} need more than the class names (\`${other}\`). Use a version of @kasperpeulen/vitest-plugin-nextjs that supports vite@${version}.`);
|
|
146
|
+
const exports = body.filter((node) => kept.has(node));
|
|
147
|
+
return exports.length > 0 ? exports.map((node) => code.slice(node.start, node.end)).join("\n") : noExports;
|
|
148
|
+
}
|
|
149
|
+
const noExports = "export {}";
|
|
150
|
+
function createStyles(options) {
|
|
151
|
+
const { getProject, environments } = options;
|
|
152
|
+
const layers = new Map(Object.entries(environments).map(([layer, name]) => [name, layer]));
|
|
153
|
+
const lists = new Set(options.lists);
|
|
154
|
+
const inlinesCss = () => getProject().config.experimental?.inlineCss === true;
|
|
155
|
+
const stylesheetIds = /* @__PURE__ */ new Map();
|
|
156
|
+
function serveAt(stylesheet) {
|
|
157
|
+
const url = stylesheet.url.split("?")[0];
|
|
158
|
+
const name = url.startsWith("/") ? url : `/${createHash("sha1").update(url).digest("hex").slice(0, 16)}.css`;
|
|
159
|
+
stylesheetIds.set(directory + name, stylesheet.id);
|
|
160
|
+
return directory + name;
|
|
161
|
+
}
|
|
162
|
+
const segments = /* @__PURE__ */ new Map();
|
|
163
|
+
function segmentsOf(route) {
|
|
164
|
+
let files = segments.get(route);
|
|
165
|
+
if (!files) {
|
|
166
|
+
files = getProject().loadRouteEntry(route).then((entry) => entry.segments);
|
|
167
|
+
segments.set(route, files);
|
|
168
|
+
files.catch(() => segments.delete(route));
|
|
169
|
+
}
|
|
170
|
+
return files;
|
|
171
|
+
}
|
|
172
|
+
const loads = /* @__PURE__ */ new Map();
|
|
173
|
+
function reach(vite, queue) {
|
|
174
|
+
const references = getPluginApi(vite.config)?.manager.clientReferenceMetaMap;
|
|
175
|
+
if (!references) throw new Error("vitest-plugin-rsc: vitestPluginNext() needs vitestPluginRSC() before it in the plugins.");
|
|
176
|
+
const graphs = {
|
|
177
|
+
rsc: vite.environments[environments.rsc],
|
|
178
|
+
browser: vite.environments[environments.browser]
|
|
179
|
+
};
|
|
180
|
+
const clientModules = /* @__PURE__ */ new Map();
|
|
181
|
+
const followed = /* @__PURE__ */ new Set();
|
|
182
|
+
const { pageExtensions = [] } = getProject().config;
|
|
183
|
+
async function load(layer, url) {
|
|
184
|
+
if (url.startsWith("/@id/")) url = url.slice(5).replace("__x00__", "\0");
|
|
185
|
+
const key = `${layer}:${url}`;
|
|
186
|
+
const inGraph = () => graphs[layer].moduleGraph.getModuleByUrl(url).catch(() => void 0);
|
|
187
|
+
if (queue) return queue(() => graphs[layer].transformRequest(url)).then(inGraph, () => void 0);
|
|
188
|
+
let loaded = loads.get(key);
|
|
189
|
+
if (loaded) {
|
|
190
|
+
const node = await loaded.module ?? await inGraph();
|
|
191
|
+
if (node && node.lastInvalidationTimestamp !== loaded.invalidated) loaded = void 0;
|
|
192
|
+
}
|
|
193
|
+
if (!loaded) {
|
|
194
|
+
const entry = {
|
|
195
|
+
module: graphs[layer].transformRequest(url).then(inGraph).catch((error) => {
|
|
196
|
+
if (error?.code === "ERR_LOAD_URL") vite.config.logger.warnOnce(`vitest-plugin-rsc: the CSS of ${url} is not linked: ${String(error)}`);
|
|
197
|
+
}).then(async (found) => {
|
|
198
|
+
entry.invalidated = (found ?? await inGraph())?.lastInvalidationTimestamp;
|
|
199
|
+
return found;
|
|
200
|
+
}),
|
|
201
|
+
invalidated: void 0
|
|
202
|
+
};
|
|
203
|
+
loaded = entry;
|
|
204
|
+
loads.set(key, loaded);
|
|
205
|
+
}
|
|
206
|
+
return loaded.module;
|
|
207
|
+
}
|
|
208
|
+
const isEnd = (id) => lists.has(id);
|
|
209
|
+
/** Has Vite's graphs know what `node` imports, as far as it goes. */
|
|
210
|
+
async function follow(layer, node) {
|
|
211
|
+
if (followed.has(node) || !node.id || isEnd(node.id)) return;
|
|
212
|
+
followed.add(node);
|
|
213
|
+
const reference = layer === "rsc" ? clientReferenceOfModule(references, node.id) : void 0;
|
|
214
|
+
if (reference) {
|
|
215
|
+
const client = await load("browser", reference.importId);
|
|
216
|
+
if (!client) return;
|
|
217
|
+
clientModules.set(node, client);
|
|
218
|
+
return follow("browser", client);
|
|
219
|
+
}
|
|
220
|
+
await Promise.all(Array.from(node.importedModules, async (imported) => {
|
|
221
|
+
if (!imported.id || isCSSRequest(imported.id) || isBuiltin(imported.id)) return;
|
|
222
|
+
if (!isCode(imported.id, pageExtensions)) return;
|
|
223
|
+
if (options.isHostPackageFile(imported.id.split("?")[0])) return;
|
|
224
|
+
const loaded = imported.transformResult ? imported : await load(layer, imported.url);
|
|
225
|
+
if (loaded) await follow(layer, loaded);
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
/** The stylesheets that `start` reaches, in the order of the imports. */
|
|
229
|
+
function collect(start) {
|
|
230
|
+
const paths = /* @__PURE__ */ new Set();
|
|
231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
232
|
+
const visit = (node) => {
|
|
233
|
+
if (seen.has(node) || !node.id || isEnd(node.id)) return;
|
|
234
|
+
seen.add(node);
|
|
235
|
+
const client = clientModules.get(node);
|
|
236
|
+
if (client) return visit(client);
|
|
237
|
+
for (const imported of node.importedModules) {
|
|
238
|
+
if (!imported.id) continue;
|
|
239
|
+
if (isLinked(imported.id)) paths.add(serveAt(imported));
|
|
240
|
+
else if (!isCSSRequest(imported.id)) visit(imported);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
start.forEach(visit);
|
|
244
|
+
return [...paths];
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
load,
|
|
248
|
+
follow,
|
|
249
|
+
collect
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The stylesheets of a route, per file of a segment: see `Stylesheets`. The
|
|
254
|
+
* node of a test is the page of its route, and no file: its stylesheets are
|
|
255
|
+
* those of the files of the host that render it, `hostFiles` of
|
|
256
|
+
* renderServer(), in their order. Under Vitest those are the setup files
|
|
257
|
+
* and the test file; see `nodeFiles` in registry.ts. With `layouts: true`
|
|
258
|
+
* the layouts of the app around it have their own.
|
|
259
|
+
*/
|
|
260
|
+
async function stylesheetsOf(vite, files, entry, inline) {
|
|
261
|
+
const route = options.pageRoutes().get(entry);
|
|
262
|
+
if (!route) return {};
|
|
263
|
+
const { load, follow, collect } = reach(vite);
|
|
264
|
+
const starts = /* @__PURE__ */ new Map();
|
|
265
|
+
await Promise.all((await segmentsOf(route)).map(async ({ file, stylesheetsKey }) => {
|
|
266
|
+
const node = await load("rsc", file);
|
|
267
|
+
if (node) starts.set(stylesheetsKey, [node]);
|
|
268
|
+
}));
|
|
269
|
+
if ("component" in route) starts.set(componentPagePath, hostFileNodes(vite, files, "the files that render a node, `hostFiles` of renderServer() or its default under Vitest,"));
|
|
270
|
+
await Promise.all([...starts.values()].flat().map((node) => follow("rsc", node)));
|
|
271
|
+
const contentOf = async (path) => inline && inlinesCss() ? (await cssOf(vite, path))?.code : void 0;
|
|
272
|
+
const fontsOf = async (path) => {
|
|
273
|
+
const request = fontRequestOf(stylesheetIds.get(path) ?? "");
|
|
274
|
+
return request === void 0 ? void 0 : (await getProject().loadFont(request)).files;
|
|
275
|
+
};
|
|
276
|
+
return Object.fromEntries(await Promise.all(Array.from(starts, async ([file, start]) => [file, await Promise.all(collect(start).map(async (path) => ({
|
|
277
|
+
path: await versioned(vite, path),
|
|
278
|
+
content: await contentOf(path),
|
|
279
|
+
fonts: await fontsOf(path)
|
|
280
|
+
})))])));
|
|
281
|
+
}
|
|
282
|
+
async function compileRoute(vite, entry) {
|
|
283
|
+
const route = options.pageRoutes().get(entry);
|
|
284
|
+
if (!route) return;
|
|
285
|
+
const queue = oneAtATime();
|
|
286
|
+
const { load, follow, collect } = reach(vite, queue);
|
|
287
|
+
const starts = (await Promise.all((await segmentsOf(route)).map(({ file }) => load("rsc", file)))).filter((node) => node !== void 0);
|
|
288
|
+
await Promise.all(starts.map((node) => follow("rsc", node)));
|
|
289
|
+
const rsc = vite.environments[environments.rsc];
|
|
290
|
+
await Promise.all(collect(starts).map((path) => Promise.all([queue(() => rsc.transformRequest(stylesheetIds.get(path))), queue(() => cssOf(vite, path))]).catch(() => {})));
|
|
291
|
+
}
|
|
292
|
+
function hostFileNodes(vite, files, named) {
|
|
293
|
+
const rsc = vite.environments[environments.rsc];
|
|
294
|
+
return files.flatMap((file) => {
|
|
295
|
+
if (!options.isHostFile(file)) {
|
|
296
|
+
vite.config.logger.warnOnce(`vitest-plugin-rsc: ${named} name ${file}, which is neither a test file or setup file of Vitest nor matched by \`host.files\` of vitestPluginNext(); an in-source test file is a file of the app. Its stylesheets are not linked.`);
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
const nodes = rsc.moduleGraph.getModulesByFile(file);
|
|
300
|
+
if (!nodes?.size) vite.config.logger.warnOnce(`vitest-plugin-rsc: ${named} name ${file}, which the rsc layer has not loaded: the stylesheets it imports are not linked.`);
|
|
301
|
+
return [...nodes ?? []];
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const fileOf = (file) => normalizePath(path.resolve(getProject().root, file));
|
|
305
|
+
async function versioned(server, path) {
|
|
306
|
+
const css = await cssOf(server, path).catch(() => null);
|
|
307
|
+
return css ? `${directory}/${versionOf(css)}${path.slice(directory.length)}` : path;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The stylesheets of the files of the host that the host uses,
|
|
311
|
+
* `linkStylesheetsOf()`: what each imports, as for a file that renders a
|
|
312
|
+
* node, in their order, each once. Such a use has no page of the app, so
|
|
313
|
+
* the page links them itself: see client-graph.ts.
|
|
314
|
+
*/
|
|
315
|
+
async function hostFilesStylesheetsOf(vite, files) {
|
|
316
|
+
const { follow, collect } = reach(vite);
|
|
317
|
+
const nodes = hostFileNodes(vite, files, "`linkStylesheetsOf()`");
|
|
318
|
+
await Promise.all(nodes.map((node) => follow("rsc", node)));
|
|
319
|
+
return linkedAt(vite, collect(nodes));
|
|
320
|
+
}
|
|
321
|
+
function linkedAt(vite, paths) {
|
|
322
|
+
const urlOf = (path) => path.split("/").map(encodeURIComponent).join("/");
|
|
323
|
+
return Promise.all(paths.map(async (path) => getProject().assetPath + urlOf(await versioned(vite, path))));
|
|
324
|
+
}
|
|
325
|
+
function changedStylesheets(graph, modules) {
|
|
326
|
+
const changed = /* @__PURE__ */ new Map();
|
|
327
|
+
if (inlinesCss()) return changed;
|
|
328
|
+
const reached = new Set(modules.flatMap((node) => [node, ...node.importers]));
|
|
329
|
+
for (const [path, id] of stylesheetIds) {
|
|
330
|
+
const css = graph.getModuleById(`${id}&direct`);
|
|
331
|
+
if (!css?.isSelfAccepting || !reached.has(css)) continue;
|
|
332
|
+
graph.invalidateModule(css);
|
|
333
|
+
changed.set(path, css);
|
|
334
|
+
}
|
|
335
|
+
return changed;
|
|
336
|
+
}
|
|
337
|
+
async function cssOf(server, path) {
|
|
338
|
+
const id = stylesheetIds.get(path);
|
|
339
|
+
return id ? server.environments[environments.rsc].transformRequest(`${id}&direct`) : null;
|
|
340
|
+
}
|
|
341
|
+
const versions = /* @__PURE__ */ new WeakMap();
|
|
342
|
+
function versionOf(css) {
|
|
343
|
+
let version = versions.get(css);
|
|
344
|
+
if (!version) {
|
|
345
|
+
version = createHash("sha1").update(css.code).update(css.map?.mappings ?? "").digest("hex").slice(0, 16);
|
|
346
|
+
versions.set(css, version);
|
|
347
|
+
}
|
|
348
|
+
return version;
|
|
349
|
+
}
|
|
350
|
+
async function serve(server, request, response) {
|
|
351
|
+
const { assetPath } = getProject();
|
|
352
|
+
const { pathname } = new URL(request.url, "http://n");
|
|
353
|
+
if (!pathname.startsWith(`${assetPath}${directory}/`)) return false;
|
|
354
|
+
let path;
|
|
355
|
+
try {
|
|
356
|
+
path = pathname.slice(assetPath.length).split("/").map(decodeURIComponent).join("/");
|
|
357
|
+
} catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const [, hash, rest] = versionRE.exec(path) ?? [];
|
|
361
|
+
const version = hash && !stylesheetIds.has(path) && stylesheetIds.has(directory + rest) ? hash : void 0;
|
|
362
|
+
if (version) path = directory + rest;
|
|
363
|
+
const css = await cssOf(server, path);
|
|
364
|
+
if (!css) return false;
|
|
365
|
+
const current = versionOf(css);
|
|
366
|
+
send(request, response, css.code, "css", {
|
|
367
|
+
etag: `"${current}"`,
|
|
368
|
+
cacheControl: version === current ? "public, max-age=31536000, immutable" : "no-cache",
|
|
369
|
+
headers: server.config.server.headers,
|
|
370
|
+
map: css.map
|
|
371
|
+
});
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
const built = createBuiltFiles(options, segmentsOf);
|
|
375
|
+
return {
|
|
376
|
+
plugins: [
|
|
377
|
+
{
|
|
378
|
+
name: "vitest-plugin-rsc:next-styles",
|
|
379
|
+
enforce: "pre",
|
|
380
|
+
configureServer(vite) {
|
|
381
|
+
answerPage(vite, stylesheetsEvent, (question) => "hostFiles" in question ? hostFilesStylesheetsOf(vite, question.hostFiles.map(fileOf)) : stylesheetsOf(vite, question.files.map(fileOf), question.entry, question.inline));
|
|
382
|
+
vite.watcher.on("all", () => {
|
|
383
|
+
for (const [key, loaded] of loads) loaded.module.then((node) => {
|
|
384
|
+
if (!node && loads.get(key) === loaded) loads.delete(key);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
vite.middlewares.use(async (request, response, next) => {
|
|
388
|
+
let served;
|
|
389
|
+
try {
|
|
390
|
+
served = await serve(vite, request, response);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
vite.config.logger.error(`vitest-plugin-rsc: ${request.url} failed. ${String(error)}`);
|
|
393
|
+
response.statusCode = 500;
|
|
394
|
+
response.end("Internal Server Error");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!served) next();
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
hotUpdate: {
|
|
401
|
+
order: "pre",
|
|
402
|
+
async handler({ modules, server }) {
|
|
403
|
+
if (!layers.has(this.environment.name)) return;
|
|
404
|
+
const moved = /* @__PURE__ */ new Set();
|
|
405
|
+
if (this.environment.name === environments.rsc) {
|
|
406
|
+
const changed = changedStylesheets(this.environment.moduleGraph, modules);
|
|
407
|
+
if (changed.size > 0) {
|
|
408
|
+
const data = await linkedAt(server, [...changed.keys()]);
|
|
409
|
+
this.environment.hot.send({
|
|
410
|
+
type: "custom",
|
|
411
|
+
event: stylesheetsChangedEvent,
|
|
412
|
+
data
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
for (const css of changed.values()) moved.add(css);
|
|
416
|
+
}
|
|
417
|
+
const updated = modules.filter((node) => !moved.has(node) && !movesItsLinks(node));
|
|
418
|
+
if (updated.length < modules.length) return updated;
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
async resolveId(source, importer, resolveOptions) {
|
|
422
|
+
const layer = layers.get(this.environment.name);
|
|
423
|
+
if (!layer || !importer || source.includes("?") || isScriptRequest(source)) return;
|
|
424
|
+
const file = importer.split("?")[0];
|
|
425
|
+
if (layer === "rsc" && !options.isServerCode(file) && !options.isHostFile(file)) return;
|
|
426
|
+
const resolved = await this.resolve(source, importer, {
|
|
427
|
+
...resolveOptions,
|
|
428
|
+
skipSelf: true
|
|
429
|
+
});
|
|
430
|
+
if (!resolved || resolved.external || resolved.id.includes("?")) return resolved;
|
|
431
|
+
if (!isCSSRequest(resolved.id)) return resolved;
|
|
432
|
+
return {
|
|
433
|
+
...resolved,
|
|
434
|
+
id: linked(resolved.id, this.environment.mode === "build")
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
transform(code, id) {
|
|
438
|
+
if (!/\.css(?:$|\?)/.test(id) || /[?&](?:raw|url)\b/.test(id)) return;
|
|
439
|
+
const declared = declareImportedLayers(code);
|
|
440
|
+
if (declared !== code) return {
|
|
441
|
+
code: declared,
|
|
442
|
+
map: null
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "vitest-plugin-rsc:next-styles-module",
|
|
448
|
+
enforce: "post",
|
|
449
|
+
applyToEnvironment: (environment) => layers.has(environment.name),
|
|
450
|
+
transform(code, id) {
|
|
451
|
+
if (!isLinked(id) || directRE.test(id)) return;
|
|
452
|
+
const exports = exportsOfStylesheet(code, id);
|
|
453
|
+
const movesLinks = exports === noExports && !inlinesCss();
|
|
454
|
+
return {
|
|
455
|
+
code: exports,
|
|
456
|
+
map: { mappings: "" },
|
|
457
|
+
meta: { [metaKey]: { movesLinks } }
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
...built.plugins
|
|
462
|
+
],
|
|
463
|
+
compileRoute,
|
|
464
|
+
builtFiles: built.files
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function oneAtATime() {
|
|
468
|
+
let last = Promise.resolve();
|
|
469
|
+
return (task) => {
|
|
470
|
+
const run = last.then(() => new Promise((resolve) => setImmediate(resolve))).then(task);
|
|
471
|
+
last = run.catch(() => {});
|
|
472
|
+
return run;
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const realRoots = /* @__PURE__ */ new Map();
|
|
476
|
+
function realRootOf(root) {
|
|
477
|
+
let real = realRoots.get(root);
|
|
478
|
+
if (real === void 0) {
|
|
479
|
+
real = fs.existsSync(root) ? normalizePath(fs.realpathSync(root)) : normalizePath(root);
|
|
480
|
+
realRoots.set(root, real);
|
|
481
|
+
}
|
|
482
|
+
return real;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* The key of a file of the host in `hostFiles` of `BuiltFiles`, by the
|
|
486
|
+
* id of its module in a build: its path from the root of the project, the
|
|
487
|
+
* same on every machine. Nothing for a file on another drive of Windows,
|
|
488
|
+
* which has no way from the root: its path is the machine's.
|
|
489
|
+
*/
|
|
490
|
+
function builtHostFileKey(id, root) {
|
|
491
|
+
const file = normalizePath(path.relative(realRootOf(normalizePath(root)), id));
|
|
492
|
+
return path.isAbsolute(file) ? void 0 : file;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The key of a module of a build, as `builtHostFileKey()` has it, when the
|
|
496
|
+
* module is a file of the host: matched by its path from the root, as
|
|
497
|
+
* `host.files` are from the root as the config has it. Nothing for a module
|
|
498
|
+
* that is no file, or one with a query.
|
|
499
|
+
*/
|
|
500
|
+
function builtHostFileOf(id, root, isHostFile) {
|
|
501
|
+
if (id.startsWith("\0") || id.includes("?")) return void 0;
|
|
502
|
+
const file = builtHostFileKey(id, root);
|
|
503
|
+
if (file === void 0 || !isHostFile(path.posix.join(normalizePath(root), file))) return;
|
|
504
|
+
return file;
|
|
505
|
+
}
|
|
506
|
+
const builtAssetRE = /__VITE_ASSET__([\w$]+)__/g;
|
|
507
|
+
const publicAssetRE = /__VITE_PUBLIC_ASSET__([a-z\d]{8})__/g;
|
|
508
|
+
const publicAssetHash = (url) => createHash("sha256").update(url).digest("hex").slice(0, 8);
|
|
509
|
+
const cssUrlRE = /\burl\(\s*(?:"([^"]*)"|'([^']*)'|([^'")\s]*))\s*\)|"(\/[^"]*)"|'(\/[^']*)'/g;
|
|
510
|
+
/**
|
|
511
|
+
* The stylesheets of a static build, which has no dev server to ask. So the
|
|
512
|
+
* build does what Next's build does: it writes each stylesheet into a file of
|
|
513
|
+
* its own under `/_next/static/css/`, and the stylesheets of every page route
|
|
514
|
+
* into a file next to the layers, which the page reads (`builtStylesheetsOf()`
|
|
515
|
+
* in styles-shared.ts).
|
|
516
|
+
*
|
|
517
|
+
* The CSS is what Vite's CSS plugin compiles the file into, with the files it
|
|
518
|
+
* names by the way from the stylesheet. Vite leaves a stylesheet that Next
|
|
519
|
+
* links out of the CSS of its chunks, see `linked()`: it is on the pages that
|
|
520
|
+
* link it, and only there. The lists are read off the module graphs of the
|
|
521
|
+
* build, as `reach()` reads them off the dev server's: the rsc layer for the
|
|
522
|
+
* files of the segments and the files of the host, and the layer of the
|
|
523
|
+
* browser for the Client Components, which is built after it. The node of a
|
|
524
|
+
* route has the stylesheets of the files of the host that the page names
|
|
525
|
+
* when it renders the node, as with the dev server: the build lists them
|
|
526
|
+
* for every file of the host it has, by its path from the root of the
|
|
527
|
+
* project, which is the same on every machine.
|
|
528
|
+
*
|
|
529
|
+
* The file also lists the modules of every page route, which a page load
|
|
530
|
+
* starts at once with the stylesheets: see route-chunks.ts.
|
|
531
|
+
*/
|
|
532
|
+
function createBuiltFiles(options, segmentsOf) {
|
|
533
|
+
const { environments, getProject } = options;
|
|
534
|
+
const lists = new Set(options.lists);
|
|
535
|
+
const layerOf = (environment) => environment === environments.rsc ? "rsc" : environment === environments.browser ? "browser" : void 0;
|
|
536
|
+
let manager;
|
|
537
|
+
let publicDir = false;
|
|
538
|
+
const key = (layer, id) => `${layer}\0${id}`;
|
|
539
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
540
|
+
const files = /* @__PURE__ */ new Map();
|
|
541
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
542
|
+
const routes = /* @__PURE__ */ new Map();
|
|
543
|
+
const hostFiles = /* @__PURE__ */ new Map();
|
|
544
|
+
const clients = /* @__PURE__ */ new Map();
|
|
545
|
+
const modules = /* @__PURE__ */ new Map();
|
|
546
|
+
const publicUrls = /* @__PURE__ */ new Map();
|
|
547
|
+
let listed = false;
|
|
548
|
+
const publicUrl = (hash) => {
|
|
549
|
+
if (!listed) {
|
|
550
|
+
listed = true;
|
|
551
|
+
const files = publicDir ? fs.readdirSync(publicDir, { recursive: true }) : [];
|
|
552
|
+
for (const file of files) {
|
|
553
|
+
const url = `/${normalizePath(String(file))}`;
|
|
554
|
+
publicUrls.set(publicAssetHash(url), url);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return publicUrls.get(hash);
|
|
558
|
+
};
|
|
559
|
+
function importsIn(context) {
|
|
560
|
+
const imports = /* @__PURE__ */ new Map();
|
|
561
|
+
return (id) => {
|
|
562
|
+
if (!imports.has(id)) {
|
|
563
|
+
const info = context.getModuleInfo(id);
|
|
564
|
+
imports.set(id, info ? [...info.importedIds, ...info.dynamicallyImportedIds] : void 0);
|
|
565
|
+
}
|
|
566
|
+
return imports.get(id);
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function reachIn(imports, starts, references = {}) {
|
|
570
|
+
const reached = [];
|
|
571
|
+
const seen = /* @__PURE__ */ new Set();
|
|
572
|
+
const visit = (id) => {
|
|
573
|
+
if (seen.has(id) || lists.has(id)) return;
|
|
574
|
+
seen.add(id);
|
|
575
|
+
const reference = references[id];
|
|
576
|
+
if (reference) {
|
|
577
|
+
reached.push({ client: reference.importId });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
for (const imported of imports(id) ?? []) if (isLinked(imported)) reached.push({ stylesheet: imported });
|
|
581
|
+
else if (!isCSSRequest(imported)) visit(imported);
|
|
582
|
+
};
|
|
583
|
+
starts.forEach(visit);
|
|
584
|
+
return reached;
|
|
585
|
+
}
|
|
586
|
+
const plugin = {
|
|
587
|
+
name: "vitest-plugin-rsc:next-styles-build",
|
|
588
|
+
apply: "build",
|
|
589
|
+
configResolved(config) {
|
|
590
|
+
manager = getPluginApi(config)?.manager;
|
|
591
|
+
publicDir = config.publicDir || false;
|
|
592
|
+
},
|
|
593
|
+
buildStart() {
|
|
594
|
+
const layer = layerOf(this.environment.name);
|
|
595
|
+
if (!layer || manager?.isScanBuild) return;
|
|
596
|
+
for (const map of [compiled, files]) for (const id of map.keys()) if (id.startsWith(key(layer, ""))) map.delete(id);
|
|
597
|
+
if (layer === "rsc") {
|
|
598
|
+
routes.clear();
|
|
599
|
+
hostFiles.clear();
|
|
600
|
+
modules.clear();
|
|
601
|
+
} else clients.clear();
|
|
602
|
+
},
|
|
603
|
+
transform(code, id) {
|
|
604
|
+
const layer = layerOf(this.environment.name);
|
|
605
|
+
if (!layer || manager?.isScanBuild || !isLinked(id) || directRE.test(id)) return;
|
|
606
|
+
compiled.set(key(layer, id), code);
|
|
607
|
+
},
|
|
608
|
+
async buildEnd(error) {
|
|
609
|
+
const layer = layerOf(this.environment.name);
|
|
610
|
+
if (error || !layer || !manager || manager.isScanBuild) return;
|
|
611
|
+
const imports = importsIn(this);
|
|
612
|
+
if (layer === "rsc") {
|
|
613
|
+
for (const [entry, route] of options.pageRoutes()) {
|
|
614
|
+
const segments = /* @__PURE__ */ new Map();
|
|
615
|
+
for (const { file, stylesheetsKey } of await segmentsOf(route)) {
|
|
616
|
+
let id = normalizePath(file);
|
|
617
|
+
if (!this.getModuleInfo(id) && fs.existsSync(file)) id = normalizePath(fs.realpathSync(file));
|
|
618
|
+
const reached = reachIn(imports, [id], manager.clientReferenceMetaMap);
|
|
619
|
+
segments.set(stylesheetsKey, reached);
|
|
620
|
+
}
|
|
621
|
+
if ("component" in route) segments.set(componentPagePath, []);
|
|
622
|
+
routes.set(entry, segments);
|
|
623
|
+
}
|
|
624
|
+
const project = getProject();
|
|
625
|
+
const { pageExtensions = [] } = project.config;
|
|
626
|
+
for (const id of this.getModuleIds()) {
|
|
627
|
+
if (!isCode(id, pageExtensions)) continue;
|
|
628
|
+
const file = builtHostFileOf(id, project.root, options.isHostFile);
|
|
629
|
+
if (file === void 0) continue;
|
|
630
|
+
hostFiles.set(file, reachIn(imports, [id], manager.clientReferenceMetaMap));
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const stylesheetsOf = (reached) => reached.flatMap((item) => "stylesheet" in item ? [item.stylesheet] : []);
|
|
635
|
+
const importer = `\0${clientReferencesId}`;
|
|
636
|
+
const reached = [...[...routes.values()].flatMap((segments) => [...segments.values()]), ...hostFiles.values()];
|
|
637
|
+
for (const item of reached.flat()) {
|
|
638
|
+
if (!("client" in item) || clients.has(item.client)) continue;
|
|
639
|
+
const resolved = await this.resolve(item.client, importer);
|
|
640
|
+
clients.set(item.client, resolved ? stylesheetsOf(reachIn(imports, [resolved.id])) : []);
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
async generateBundle(_options, bundle) {
|
|
644
|
+
const layer = layerOf(this.environment.name);
|
|
645
|
+
if (!layer || manager?.isScanBuild) return;
|
|
646
|
+
if (layer === "rsc") {
|
|
647
|
+
const routes = Array.from(options.pageRoutes().keys(), (route) => {
|
|
648
|
+
const entry = options.entryIdOf(route);
|
|
649
|
+
return [route, [entry, ...this.getModuleInfo(entry)?.dynamicallyImportedIds ?? []]];
|
|
650
|
+
});
|
|
651
|
+
for (const [route, chunks] of routeChunksOf(bundle, [...lists], routes)) modules.set(route, chunks);
|
|
652
|
+
}
|
|
653
|
+
const directory = `${getProject().assetPath.slice(1)}static/css/`;
|
|
654
|
+
const toRoot = toBuildDir(`${directory}stylesheet.css`);
|
|
655
|
+
for (const [id, css] of compiled) {
|
|
656
|
+
if (!id.startsWith(key(layer, ""))) continue;
|
|
657
|
+
const source = css.replace(builtAssetRE, (_, reference) => toRoot + this.getFileName(reference)).replace(publicAssetRE, (placeholder, hash) => {
|
|
658
|
+
const url = publicUrl(hash);
|
|
659
|
+
return url ? toRoot + url.slice(1) : placeholder;
|
|
660
|
+
}).replaceAll(`${cssBuildDirPlaceholder}/`, toRoot);
|
|
661
|
+
const file = id.slice(key(layer, "").length).split("?")[0];
|
|
662
|
+
const name = file.startsWith("\0") ? "font" : path.basename(file).replace(/\.[^.]*$/, "");
|
|
663
|
+
const hash = createHash("sha256").update(source).digest("hex").slice(0, 8);
|
|
664
|
+
const fileName = `${directory}${name.replace(/[^\w.-]+/g, "_")}-${hash}.css`;
|
|
665
|
+
this.emitFile({
|
|
666
|
+
type: "asset",
|
|
667
|
+
fileName,
|
|
668
|
+
source
|
|
669
|
+
});
|
|
670
|
+
files.set(id, fileName);
|
|
671
|
+
const request = fontRequestOf(file);
|
|
672
|
+
if (request !== void 0) {
|
|
673
|
+
const { files: fontFiles } = await getProject().loadFont(request);
|
|
674
|
+
const assetDirectory = getProject().assetPath.slice(1);
|
|
675
|
+
fonts.set(fileName, fontFiles.map((font) => assetDirectory + font));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const urls = {
|
|
681
|
+
name: "vitest-plugin-rsc:next-styles-build-urls",
|
|
682
|
+
apply: "build",
|
|
683
|
+
enforce: "pre",
|
|
684
|
+
transform(code, id) {
|
|
685
|
+
if (!layerOf(this.environment.name) || !isLinked(id) || directRE.test(id)) return;
|
|
686
|
+
for (const match of code.matchAll(cssUrlRE)) {
|
|
687
|
+
const url = match.slice(1).find((group) => group !== void 0).replace(/\\(\W)/g, "$1");
|
|
688
|
+
if (!url.startsWith("/") || url.startsWith("//")) continue;
|
|
689
|
+
try {
|
|
690
|
+
const decoded = decodeURI(url);
|
|
691
|
+
publicUrls.set(publicAssetHash(decoded), decoded);
|
|
692
|
+
} catch {}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
/**
|
|
697
|
+
* The stylesheets of every page route and every file of the host, and the
|
|
698
|
+
* modules of every page route, by the files of the build.
|
|
699
|
+
*/
|
|
700
|
+
function builtFiles() {
|
|
701
|
+
if (routes.size === 0) return [];
|
|
702
|
+
const built = {
|
|
703
|
+
assetPath: getProject().assetPath,
|
|
704
|
+
routes: {},
|
|
705
|
+
hostFiles: {},
|
|
706
|
+
chunks: [],
|
|
707
|
+
modules: {},
|
|
708
|
+
fonts: Object.fromEntries([...fonts].sort(([a], [b]) => a.localeCompare(b)))
|
|
709
|
+
};
|
|
710
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
711
|
+
for (const [route, chunks] of modules) built.modules[route] = chunks.map((chunk) => {
|
|
712
|
+
if (!indexes.has(chunk)) indexes.set(chunk, built.chunks.push(chunk) - 1);
|
|
713
|
+
return indexes.get(chunk);
|
|
714
|
+
});
|
|
715
|
+
const filesOf = (ids) => [...new Set(ids.flatMap((id) => files.get(id) ?? []))];
|
|
716
|
+
const idsOf = (reached) => reached.flatMap((item) => "stylesheet" in item ? [key("rsc", item.stylesheet)] : (clients.get(item.client) ?? []).map((id) => key("browser", id)));
|
|
717
|
+
for (const [entry, segments] of routes) built.routes[entry] = Object.fromEntries(Array.from(segments, ([segment, reached]) => [segment, filesOf(idsOf(reached))]));
|
|
718
|
+
for (const file of [...hostFiles.keys()].sort()) built.hostFiles[file] = filesOf(idsOf(hostFiles.get(file)));
|
|
719
|
+
return [{
|
|
720
|
+
pathname: `/${builtFilesPath}`,
|
|
721
|
+
body: Buffer.from(JSON.stringify(built))
|
|
722
|
+
}];
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
plugins: [urls, plugin],
|
|
726
|
+
files: builtFiles
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
//#endregion
|
|
730
|
+
export { builtHostFileKey, builtHostFileOf, changesInPlace, createStyles, declareImportedLayers, exportsOfStylesheet, isCode, linked, movesItsLinks };
|