@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,71 @@
|
|
|
1
|
+
import { NextBuild } from "../build.js";
|
|
2
|
+
import { NextContext } from "./context.js";
|
|
3
|
+
import { NextProject } from "../project.js";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/project/loaders.d.ts
|
|
7
|
+
type LoaderContext = {
|
|
8
|
+
getOptions(): unknown; /** The options as a loader of webpack 4 reads them. */
|
|
9
|
+
query: unknown;
|
|
10
|
+
mode: "development" | "production";
|
|
11
|
+
_module: {
|
|
12
|
+
buildInfo: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
async(): (error: Error | null, ...result: unknown[]) => void;
|
|
15
|
+
callback(error: Error | null, ...result: unknown[]): void;
|
|
16
|
+
cacheable(flag?: boolean): void;
|
|
17
|
+
currentTraceSpan: TraceSpan;
|
|
18
|
+
resourcePath: string;
|
|
19
|
+
resourceQuery: string;
|
|
20
|
+
context: string;
|
|
21
|
+
rootContext: string;
|
|
22
|
+
emitFile(name: string, content: Buffer): void;
|
|
23
|
+
emitWarning(warning: Error): void;
|
|
24
|
+
emitError(error: Error): void;
|
|
25
|
+
addDependency(file: string): void;
|
|
26
|
+
addContextDependency(directory: string): void;
|
|
27
|
+
resolve(directory: string, request: string, callback: (error: Error | null, file?: string) => void): void;
|
|
28
|
+
getResolve(): () => Promise<never>;
|
|
29
|
+
loadModule(request: string, callback: (error: Error | null, source?: string, map?: null, module?: LoadedModule) => void): void;
|
|
30
|
+
fs: typeof fs;
|
|
31
|
+
utils: {
|
|
32
|
+
contextify(context: string, request: string): string;
|
|
33
|
+
};
|
|
34
|
+
sourceMap: boolean;
|
|
35
|
+
};
|
|
36
|
+
type TraceSpan = {
|
|
37
|
+
traceChild(): TraceSpan;
|
|
38
|
+
traceFn<T>(fn: () => T): T;
|
|
39
|
+
traceAsyncFn<T>(fn: () => T): T;
|
|
40
|
+
setAttribute(): void;
|
|
41
|
+
};
|
|
42
|
+
type Loader = (this: LoaderContext, ...input: unknown[]) => unknown;
|
|
43
|
+
type LoadedModule = {
|
|
44
|
+
dependencies: {
|
|
45
|
+
constructor: {
|
|
46
|
+
name: "HarmonyExportSpecifierDependency";
|
|
47
|
+
};
|
|
48
|
+
name: string;
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
/** Calls a webpack loader of Next for one module, the way webpack does. */
|
|
52
|
+
type RunLoader = (loader: Loader, options: unknown, resource: string, ...input: unknown[]) => Promise<unknown[]>;
|
|
53
|
+
/**
|
|
54
|
+
* `sassOptions` of `next.config`, as Vite's options of Sass. Next gives
|
|
55
|
+
* sass-loader the options of Sass, and `additionalData`, or `prependData`,
|
|
56
|
+
* which goes before the source of each file, as it does for Vite. A path to
|
|
57
|
+
* load from is one of the project, where Next's build runs. Not
|
|
58
|
+
* `implementation`: Vite takes `sass-embedded` where it is installed, and
|
|
59
|
+
* `sass` otherwise.
|
|
60
|
+
*/
|
|
61
|
+
declare function sassPreprocessorOptions(sassOptions: Record<string, unknown>, root: string): Record<string, unknown> | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Next's webpack loaders for fonts and images, what they emit for the
|
|
64
|
+
* browser, and Next's image optimizer. `build` is the code that Next's
|
|
65
|
+
* runtime and React run, see build.ts.
|
|
66
|
+
*/
|
|
67
|
+
declare function createLoaders(context: NextContext, build: NextBuild): Pick<NextProject, "isImage" | "loadImage" | "loadFont" | "loadCssOptions" | "assetPath" | "readEmittedFile" | "emittedFiles" | "optimizeImage" | "loadRootParams"> & {
|
|
68
|
+
runLoader: RunLoader;
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
export { Loader, LoaderContext, RunLoader, createLoaders, sassPreprocessorOptions };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { fontLoaderPath } from "../compat/paths.js";
|
|
2
|
+
import { exportsOf } from "./module-exports.js";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import querystring from "node:querystring";
|
|
7
|
+
import { compileFunction } from "node:vm";
|
|
8
|
+
//#region src/project/loaders.ts
|
|
9
|
+
function hasOtherPostCssConfig(root) {
|
|
10
|
+
const names = (directory) => {
|
|
11
|
+
try {
|
|
12
|
+
return fs.readdirSync(directory);
|
|
13
|
+
} catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
return names(root).some((name) => /^(?:\.postcssrc(?:\..+)?|postcss\.config\..+)$/.test(name)) || names(path.join(root, ".config")).some((name) => name.startsWith("postcssrc"));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `sassOptions` of `next.config`, as Vite's options of Sass. Next gives
|
|
21
|
+
* sass-loader the options of Sass, and `additionalData`, or `prependData`,
|
|
22
|
+
* which goes before the source of each file, as it does for Vite. A path to
|
|
23
|
+
* load from is one of the project, where Next's build runs. Not
|
|
24
|
+
* `implementation`: Vite takes `sass-embedded` where it is installed, and
|
|
25
|
+
* `sass` otherwise.
|
|
26
|
+
*/
|
|
27
|
+
function sassPreprocessorOptions(sassOptions, root) {
|
|
28
|
+
const { prependData, additionalData, implementation: _, loadPaths, ...options } = sassOptions;
|
|
29
|
+
const data = prependData ?? additionalData;
|
|
30
|
+
const sass = {
|
|
31
|
+
...options,
|
|
32
|
+
...Array.isArray(loadPaths) && { loadPaths: loadPaths.map((entry) => path.resolve(root, String(entry))) },
|
|
33
|
+
...data !== void 0 && { additionalData: typeof data === "function" ? (source, file) => data(source, {
|
|
34
|
+
resourcePath: file,
|
|
35
|
+
rootContext: root
|
|
36
|
+
}) : data }
|
|
37
|
+
};
|
|
38
|
+
return Object.keys(sass).length > 0 ? sass : void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Next's webpack loaders for fonts and images, what they emit for the
|
|
42
|
+
* browser, and Next's image optimizer. `build` is the code that Next's
|
|
43
|
+
* runtime and React run, see build.ts.
|
|
44
|
+
*/
|
|
45
|
+
function createLoaders(context, build) {
|
|
46
|
+
const { root, projectRequire, nextDir, fail, config, next } = context;
|
|
47
|
+
const { COMPILER_NAMES, getContentType, getExtension, getNextFontLoader, getPostCssPlugins, getSupportedBrowsers, getCssModuleLoader, localByDefault, findConfig, imageOptimizer, nextFontLoader, nextRootParamsLoader, nextImageLoader, nextImageLoaderRegex } = next;
|
|
48
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
49
|
+
const span = {
|
|
50
|
+
traceChild: () => span,
|
|
51
|
+
traceFn: (fn) => fn(),
|
|
52
|
+
traceAsyncFn: (fn) => fn(),
|
|
53
|
+
setAttribute: () => {}
|
|
54
|
+
};
|
|
55
|
+
const runLoaderEmitting = (emits) => (loader, options, resource, ...input) => new Promise((resolve, reject) => {
|
|
56
|
+
const queryAt = resource.indexOf("?");
|
|
57
|
+
const resourcePath = queryAt < 0 ? resource : resource.slice(0, queryAt);
|
|
58
|
+
let callsBack = false;
|
|
59
|
+
const callback = (error, ...result) => error ? reject(error) : resolve(result);
|
|
60
|
+
const context = {
|
|
61
|
+
getOptions: () => options ?? {},
|
|
62
|
+
query: options ?? {},
|
|
63
|
+
mode: build,
|
|
64
|
+
_module: { buildInfo: {} },
|
|
65
|
+
async: () => {
|
|
66
|
+
callsBack = true;
|
|
67
|
+
return callback;
|
|
68
|
+
},
|
|
69
|
+
callback: (...args) => {
|
|
70
|
+
callsBack = true;
|
|
71
|
+
callback(...args);
|
|
72
|
+
},
|
|
73
|
+
cacheable: () => {},
|
|
74
|
+
currentTraceSpan: span,
|
|
75
|
+
resourcePath,
|
|
76
|
+
resourceQuery: queryAt < 0 ? "" : resource.slice(queryAt),
|
|
77
|
+
context: path.dirname(resourcePath),
|
|
78
|
+
rootContext: root,
|
|
79
|
+
emitFile: (name, content) => {
|
|
80
|
+
const file = name.replace(/^\//, "");
|
|
81
|
+
emitted.set(file, content);
|
|
82
|
+
emits?.(file);
|
|
83
|
+
},
|
|
84
|
+
emitWarning: (warning) => console.warn(warning),
|
|
85
|
+
emitError: reject,
|
|
86
|
+
addDependency: () => {},
|
|
87
|
+
addContextDependency: () => {},
|
|
88
|
+
resolve: (directory, request, callback) => {
|
|
89
|
+
const file = path.resolve(directory, request);
|
|
90
|
+
if (fs.existsSync(file)) callback(null, file);
|
|
91
|
+
else callback(/* @__PURE__ */ new Error(`Can't resolve '${request}' in '${directory}'`));
|
|
92
|
+
},
|
|
93
|
+
getResolve: () => () => Promise.reject(/* @__PURE__ */ new Error("Not resolved by vitest-plugin-rsc")),
|
|
94
|
+
loadModule: (request, respond) => {
|
|
95
|
+
const read = async () => {
|
|
96
|
+
const source = await fs.promises.readFile(request, "utf8");
|
|
97
|
+
const { names } = await exportsOf(source, request);
|
|
98
|
+
return {
|
|
99
|
+
source,
|
|
100
|
+
module: { dependencies: [...names].filter((name) => name !== "default").map((name) => ({
|
|
101
|
+
constructor: { name: "HarmonyExportSpecifierDependency" },
|
|
102
|
+
name
|
|
103
|
+
})) }
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
read().then(({ source, module }) => respond(null, source, null, module), (error) => respond(error));
|
|
107
|
+
},
|
|
108
|
+
fs,
|
|
109
|
+
utils: { contextify: (_context, request) => request },
|
|
110
|
+
sourceMap: false
|
|
111
|
+
};
|
|
112
|
+
Promise.resolve(loader.call(context, ...input)).then((result) => callsBack || resolve([result]), reject);
|
|
113
|
+
});
|
|
114
|
+
const runLoader = runLoaderEmitting();
|
|
115
|
+
const resolve = (id) => {
|
|
116
|
+
try {
|
|
117
|
+
return projectRequire.resolve(id);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return fail(`${id} is not there`, error);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
let postcss;
|
|
123
|
+
try {
|
|
124
|
+
postcss = createRequire(path.join(nextDir, "package.json"))("postcss");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
fail("it no longer depends on `postcss`", error);
|
|
127
|
+
}
|
|
128
|
+
const fontLoaders = ["google", "local"].map((name) => {
|
|
129
|
+
const target = resolve(`next/font/${name}/target.css`);
|
|
130
|
+
const loaders = getNextFontLoader({
|
|
131
|
+
hasAppDir: true,
|
|
132
|
+
isClient: true,
|
|
133
|
+
isServer: false,
|
|
134
|
+
isDevelopment: true,
|
|
135
|
+
assetPrefix: config.assetPrefix,
|
|
136
|
+
deploymentId: config.deploymentId,
|
|
137
|
+
experimental: config.experimental
|
|
138
|
+
}, async () => ({ postcss }), resolve(fontLoaderPath(name)));
|
|
139
|
+
const cssLoader = loaders.find((entry) => entry.loader?.includes("css-loader"));
|
|
140
|
+
const fontLoader = loaders.find((entry) => entry.loader === "next-font-loader");
|
|
141
|
+
if (!cssLoader?.loader || !fontLoader) fail("`getNextFontLoader()` no longer uses css-loader and next-font-loader");
|
|
142
|
+
return {
|
|
143
|
+
prefix: `next/font/${name}/target.css?`,
|
|
144
|
+
target,
|
|
145
|
+
cssLoader: projectRequire(cssLoader.loader).default,
|
|
146
|
+
cssOptions: cssLoader.options,
|
|
147
|
+
fontOptions: fontLoader.options
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
151
|
+
const loadFont = async (request) => {
|
|
152
|
+
const font = fontLoaders.find((candidate) => request.startsWith(candidate.prefix));
|
|
153
|
+
const resource = font.target + request.slice(font.prefix.length - 1);
|
|
154
|
+
const files = [];
|
|
155
|
+
const [css, map, meta] = await runLoaderEmitting((file) => {
|
|
156
|
+
if (/\.(?:woff|woff2|eot|ttf|otf)$/.test(file)) files.push(file);
|
|
157
|
+
})(nextFontLoader, font.fontOptions, resource);
|
|
158
|
+
const [code] = await runLoader(font.cssLoader, font.cssOptions, resource, css, map, meta);
|
|
159
|
+
const cssModule = {
|
|
160
|
+
id: resource,
|
|
161
|
+
exports: {}
|
|
162
|
+
};
|
|
163
|
+
compileFunction(code, [
|
|
164
|
+
"module",
|
|
165
|
+
"exports",
|
|
166
|
+
"require"
|
|
167
|
+
])(cssModule, cssModule.exports, projectRequire);
|
|
168
|
+
const list = cssModule.exports;
|
|
169
|
+
if (!Array.isArray(list) || !list.locals) fail("css-loader no longer makes a list of CSS with `locals` of a `next/font` call");
|
|
170
|
+
return {
|
|
171
|
+
css: String(list),
|
|
172
|
+
exports: list.locals,
|
|
173
|
+
files
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const [cssModuleLoader] = getCssModuleLoader({
|
|
177
|
+
isClient: false,
|
|
178
|
+
isServer: true,
|
|
179
|
+
hasAppDir: true,
|
|
180
|
+
isAppDir: true,
|
|
181
|
+
isDevelopment: false,
|
|
182
|
+
experimental: {
|
|
183
|
+
...config.experimental,
|
|
184
|
+
useLightningcss: false
|
|
185
|
+
}
|
|
186
|
+
}, postcss).filter((entry) => entry.loader?.includes("css-loader"));
|
|
187
|
+
const rule = cssModuleLoader?.options?.modules;
|
|
188
|
+
const mode = typeof rule?.mode === "string" ? rule.mode : fail("`getCssModuleLoader()` no longer gives css-loader the `mode` of a CSS module");
|
|
189
|
+
const getLocalIdent = typeof rule?.getLocalIdent === "function" ? rule.getLocalIdent : fail("`getCssModuleLoader()` no longer gives css-loader a `getLocalIdent`");
|
|
190
|
+
const checkMode = {
|
|
191
|
+
postcssPlugin: "vitest-plugin-rsc:next-css-module-mode",
|
|
192
|
+
Once(root, { result, postcss: process }) {
|
|
193
|
+
const file = (result.opts.from ?? "").split("?")[0];
|
|
194
|
+
if (!/\.module\.(?:css|scss|sass)$/.test(file)) return;
|
|
195
|
+
process([localByDefault({ mode })]).process(root.clone(), { from: file }).root;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const loadCssOptions = async () => {
|
|
199
|
+
const { experimental } = config;
|
|
200
|
+
const differences = [];
|
|
201
|
+
if (experimental.useLightningcss) differences.push("`experimental.useLightningcss` is not supported: the CSS is compiled with PostCSS");
|
|
202
|
+
let postcss;
|
|
203
|
+
if (await findConfig(root, "postcss") || !hasOtherPostCssConfig(root)) postcss = { plugins: [...await getPostCssPlugins(root, getSupportedBrowsers(root, false), experimental.disablePostcssPresetEnv), checkMode] };
|
|
204
|
+
else differences.push("the app's PostCSS config is one only Vite reads: a CSS module is not held to Next's mode for it");
|
|
205
|
+
const sass = sassPreprocessorOptions(config.sassOptions ?? {}, root);
|
|
206
|
+
return {
|
|
207
|
+
options: {
|
|
208
|
+
postcss,
|
|
209
|
+
...sass && { preprocessorOptions: {
|
|
210
|
+
scss: sass,
|
|
211
|
+
sass
|
|
212
|
+
} },
|
|
213
|
+
modules: { generateScopedName: (name, file) => getLocalIdent({
|
|
214
|
+
rootContext: root,
|
|
215
|
+
resourcePath: file.split("?")[0]
|
|
216
|
+
}, void 0, name, { context: root }) }
|
|
217
|
+
},
|
|
218
|
+
differences
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
const emittedPath = `${config.assetPrefix.startsWith("/") ? config.assetPrefix : ""}/_next/`;
|
|
222
|
+
const { images } = config;
|
|
223
|
+
return {
|
|
224
|
+
runLoader,
|
|
225
|
+
isImage: (file) => !images.disableStaticImages && nextImageLoaderRegex.test(file),
|
|
226
|
+
async loadImage(file) {
|
|
227
|
+
const options = {
|
|
228
|
+
isDev: false,
|
|
229
|
+
compilerType: COMPILER_NAMES.client,
|
|
230
|
+
assetPrefix: config.assetPrefix,
|
|
231
|
+
basePath: config.basePath,
|
|
232
|
+
outputHashSalt: config.outputHashSalt
|
|
233
|
+
};
|
|
234
|
+
const [code] = await runLoader(nextImageLoader, options, file, await fs.promises.readFile(file));
|
|
235
|
+
if (typeof code !== "string" || !code.startsWith("export default {")) fail("next-image-loader no longer exports the data of an image");
|
|
236
|
+
return code;
|
|
237
|
+
},
|
|
238
|
+
loadCssOptions,
|
|
239
|
+
loadFont(request) {
|
|
240
|
+
let font = fonts.get(request);
|
|
241
|
+
if (!font) {
|
|
242
|
+
fonts.set(request, font = loadFont(request));
|
|
243
|
+
font.catch(() => fonts.delete(request));
|
|
244
|
+
}
|
|
245
|
+
return font;
|
|
246
|
+
},
|
|
247
|
+
assetPath: emittedPath,
|
|
248
|
+
async loadRootParams() {
|
|
249
|
+
const options = {
|
|
250
|
+
appDir: context.appDir,
|
|
251
|
+
pageExtensions: config.pageExtensions
|
|
252
|
+
};
|
|
253
|
+
const [code] = await runLoader(nextRootParamsLoader, options, context.appDir);
|
|
254
|
+
return String(code);
|
|
255
|
+
},
|
|
256
|
+
emittedFiles: () => [...emitted].map(([name, body]) => ({
|
|
257
|
+
pathname: `${emittedPath}${name}`,
|
|
258
|
+
body
|
|
259
|
+
})),
|
|
260
|
+
readEmittedFile(pathname) {
|
|
261
|
+
if (!pathname.startsWith(emittedPath)) return;
|
|
262
|
+
const name = pathname.slice(emittedPath.length);
|
|
263
|
+
const body = emitted.get(name);
|
|
264
|
+
if (!body) return;
|
|
265
|
+
return {
|
|
266
|
+
body,
|
|
267
|
+
contentType: getContentType(path.extname(name).slice(1)) ?? ""
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
async optimizeImage(request, response, serveFile) {
|
|
271
|
+
const url = new URL(request.url, "http://n");
|
|
272
|
+
if (url.pathname !== images.path) return false;
|
|
273
|
+
if (images.loader !== "default" || images.unoptimized) {
|
|
274
|
+
response.statusCode = 404;
|
|
275
|
+
response.end();
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
const { ImageOptimizerCache, ImageError } = imageOptimizer;
|
|
279
|
+
const query = querystring.parse(url.search.slice(1));
|
|
280
|
+
const params = ImageOptimizerCache.validateParams(request, query, config, false);
|
|
281
|
+
if ("errorMessage" in params) {
|
|
282
|
+
response.statusCode = 400;
|
|
283
|
+
response.end(params.errorMessage);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const upstream = params.isAbsolute ? await imageOptimizer.fetchExternalImage(params.href, images.dangerouslyAllowLocalIP, images.maximumResponseBody, images.maximumRedirects) : await imageOptimizer.fetchInternalImage(params.href, request, response, images.maximumResponseBody, serveFile);
|
|
288
|
+
const { buffer, contentType, maxAge, etag } = await imageOptimizer.imageOptimizer(upstream, params, config, { isDev: false });
|
|
289
|
+
imageOptimizer.sendResponse(request, response, params.href, getExtension(contentType), buffer, etag, params.isStatic, "MISS", images, maxAge, true);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (!(error instanceof ImageError)) throw error;
|
|
292
|
+
response.statusCode = error.statusCode;
|
|
293
|
+
response.end(error.message);
|
|
294
|
+
}
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
export { createLoaders, sassPreprocessorOptions };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RunLoader } from "./loaders.js";
|
|
2
|
+
import { NextContext } from "./context.js";
|
|
3
|
+
import { NextProject } from "../project.js";
|
|
4
|
+
|
|
5
|
+
//#region src/project/metadata.d.ts
|
|
6
|
+
declare function createMetadataLoaders(context: NextContext, runLoader: RunLoader): Pick<NextProject, "metadataImageMeta" | "isMetadataRequest" | "loadMetadata">;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createMetadataLoaders };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import querystring from "node:querystring";
|
|
3
|
+
//#region src/project/metadata.ts
|
|
4
|
+
const metadataRequest = /^(next-metadata-(?:image|route)-loader)\?([^!]*)!(.*)$/;
|
|
5
|
+
function createMetadataLoaders(context, runLoader) {
|
|
6
|
+
const { fail, replace, next } = context;
|
|
7
|
+
const { nextMetadataImageLoader, nextMetadataRouteLoader, WEBPACK_RESOURCE_QUERIES } = next;
|
|
8
|
+
return {
|
|
9
|
+
metadataImageMeta: `?${WEBPACK_RESOURCE_QUERIES.metadataImageMeta}`,
|
|
10
|
+
isMetadataRequest: (source) => metadataRequest.test(source),
|
|
11
|
+
async loadMetadata(request) {
|
|
12
|
+
const [, loader, query = "", resource = ""] = metadataRequest.exec(request) ?? fail(`${request} is no request for a metadata loader`);
|
|
13
|
+
const options = querystring.parse(query);
|
|
14
|
+
if (loader === "next-metadata-route-loader") {
|
|
15
|
+
const file = String(options.filePath);
|
|
16
|
+
let [code] = await runLoader(nextMetadataRouteLoader, options, resource);
|
|
17
|
+
if (options.isDynamicRouteExtension !== "1" && process.env.NODE_ENV !== "production") code = replace(code, JSON.stringify("no-cache, no-store"), JSON.stringify("public, max-age=0, must-revalidate"), "the route of a static metadata file");
|
|
18
|
+
return {
|
|
19
|
+
code,
|
|
20
|
+
watchFiles: [file]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const file = resource.split("?")[0];
|
|
24
|
+
const content = await fs.promises.readFile(file);
|
|
25
|
+
const [code] = await runLoader(nextMetadataImageLoader, options, resource, content);
|
|
26
|
+
return {
|
|
27
|
+
code,
|
|
28
|
+
watchFiles: [file]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { createMetadataLoaders };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/project/module-exports.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The names an ES module exports in its own code, `default` among them, and
|
|
4
|
+
* the modules of its `export *`. Of JavaScript, like Next's compiled files and
|
|
5
|
+
* the modules the plugin generates.
|
|
6
|
+
*/
|
|
7
|
+
declare function declaredExports(code: string): {
|
|
8
|
+
names: Set<string>;
|
|
9
|
+
stars: string[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* What an ES module of Next exports: declared there, exported apart from its
|
|
13
|
+
* declaration, or from another module of Next with `export ... from` or
|
|
14
|
+
* `export *`. A module of CommonJS has none here.
|
|
15
|
+
*/
|
|
16
|
+
declare function exportNamesOf(file: string, seen?: Set<string>): Set<string>;
|
|
17
|
+
/** Whether an ES module of Next exports a name: see `exportNamesOf()`. */
|
|
18
|
+
declare const hasExport: (file: string, name: string) => boolean;
|
|
19
|
+
/**
|
|
20
|
+
* What a module of the app exports, read off its code: the names, `default`
|
|
21
|
+
* among them, and the modules of its `export *`, whose names are theirs.
|
|
22
|
+
*/
|
|
23
|
+
declare function exportsOf(code: string, file: string): Promise<{
|
|
24
|
+
names: Set<string>;
|
|
25
|
+
stars: string[];
|
|
26
|
+
}>;
|
|
27
|
+
declare function exportStubs(code: string, file: string): Promise<string>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { declaredExports, exportNamesOf, exportStubs, exportsOf, hasExport };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { moduleFileAt } from "./context.js";
|
|
2
|
+
import { parseAst, transformWithOxc } from "vite";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
//#region src/project/module-exports.ts
|
|
6
|
+
function nameOf(node) {
|
|
7
|
+
return "name" in node ? String(node.name) : "value" in node ? String(node.value) : "";
|
|
8
|
+
}
|
|
9
|
+
function bound(pattern, names) {
|
|
10
|
+
if (!pattern || !("type" in pattern)) return;
|
|
11
|
+
if (pattern.type === "Identifier") names.add(nameOf(pattern));
|
|
12
|
+
for (const key of [
|
|
13
|
+
"properties",
|
|
14
|
+
"elements",
|
|
15
|
+
"value",
|
|
16
|
+
"argument",
|
|
17
|
+
"left"
|
|
18
|
+
]) {
|
|
19
|
+
const child = pattern[key];
|
|
20
|
+
for (const part of [child].flat()) if (typeof part === "object") bound(part, names);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The names an ES module exports in its own code, `default` among them, and
|
|
25
|
+
* the modules of its `export *`. Of JavaScript, like Next's compiled files and
|
|
26
|
+
* the modules the plugin generates.
|
|
27
|
+
*/
|
|
28
|
+
function declaredExports(code) {
|
|
29
|
+
const names = /* @__PURE__ */ new Set();
|
|
30
|
+
const stars = [];
|
|
31
|
+
for (const node of parseAst(code).body) if (node.type === "ExportDefaultDeclaration") names.add("default");
|
|
32
|
+
else if (node.type === "ExportAllDeclaration") {
|
|
33
|
+
if (node.exported) names.add(nameOf(node.exported));
|
|
34
|
+
else stars.push(node.source.value);
|
|
35
|
+
} else if (node.type === "ExportNamedDeclaration") {
|
|
36
|
+
for (const specifier of node.specifiers) names.add(nameOf(specifier.exported));
|
|
37
|
+
const { declaration } = node;
|
|
38
|
+
if (declaration?.type === "VariableDeclaration") for (const { id } of declaration.declarations) bound(id, names);
|
|
39
|
+
else if (declaration && "id" in declaration && declaration.id) names.add(nameOf(declaration.id));
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
names,
|
|
43
|
+
stars
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What an ES module of Next exports: declared there, exported apart from its
|
|
48
|
+
* declaration, or from another module of Next with `export ... from` or
|
|
49
|
+
* `export *`. A module of CommonJS has none here.
|
|
50
|
+
*/
|
|
51
|
+
function exportNamesOf(file, seen = /* @__PURE__ */ new Set()) {
|
|
52
|
+
if (seen.has(file)) return /* @__PURE__ */ new Set();
|
|
53
|
+
seen.add(file);
|
|
54
|
+
const { names, stars } = declaredExports(fs.readFileSync(file, "utf8"));
|
|
55
|
+
for (const star of stars) {
|
|
56
|
+
const from = star.startsWith(".") && moduleFileAt(path.resolve(path.dirname(file), star));
|
|
57
|
+
if (!from) continue;
|
|
58
|
+
for (const name of exportNamesOf(from, seen)) if (name !== "default") names.add(name);
|
|
59
|
+
}
|
|
60
|
+
return names;
|
|
61
|
+
}
|
|
62
|
+
/** Whether an ES module of Next exports a name: see `exportNamesOf()`. */
|
|
63
|
+
const hasExport = (file, name) => exportNamesOf(file).has(name);
|
|
64
|
+
/**
|
|
65
|
+
* What a module of the app exports, read off its code: the names, `default`
|
|
66
|
+
* among them, and the modules of its `export *`, whose names are theirs.
|
|
67
|
+
*/
|
|
68
|
+
async function exportsOf(code, file) {
|
|
69
|
+
return declaredExports((await transformWithOxc(code, file, { sourcemap: false })).code);
|
|
70
|
+
}
|
|
71
|
+
async function exportStubs(code, file) {
|
|
72
|
+
const { names, stars } = await exportsOf(code, file);
|
|
73
|
+
const named = [...names].filter((name) => /^[\w$]+$/.test(name) && name !== "default");
|
|
74
|
+
return (named.length > 0 ? `const _ = undefined;\nexport { ${named.map((name) => `_ as ${name}`).join(", ")} };\n` : "") + (names.has("default") ? "export default undefined;\n" : "") + stars.map((star) => `export * from ${JSON.stringify(star)};\n`).join("");
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { declaredExports, exportNamesOf, exportStubs, exportsOf, hasExport };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextContext } from "./context.js";
|
|
2
|
+
import { NextRoute } from "../project.js";
|
|
3
|
+
|
|
4
|
+
//#region src/project/routes.d.ts
|
|
5
|
+
/** The routes of the app, as `discoverAppRoutes()` lists them. */
|
|
6
|
+
type AppRoutes = Awaited<ReturnType<typeof discoverAppRoutes>>;
|
|
7
|
+
/**
|
|
8
|
+
* The routes of the app, the way `next build` lists its entries, and what
|
|
9
|
+
* `next build` rejects an app for.
|
|
10
|
+
*/
|
|
11
|
+
declare function discoverAppRoutes(context: NextContext): Promise<{
|
|
12
|
+
routes: NextRoute[]; /** The pages of each route, by its pathname: a route can have a page in each of its slots. */
|
|
13
|
+
appPathsPerRoute: Record<string, string[]>;
|
|
14
|
+
strictRouteMatching: true | undefined;
|
|
15
|
+
edgeRouteFiles: string[];
|
|
16
|
+
routeFile: (route: NextRoute) => string | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppRoutes, discoverAppRoutes };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
//#region src/project/routes.ts
|
|
4
|
+
/**
|
|
5
|
+
* The routes of the app, the way `next build` lists its entries, and what
|
|
6
|
+
* `next build` rejects an app for.
|
|
7
|
+
*/
|
|
8
|
+
async function discoverAppRoutes(context) {
|
|
9
|
+
const { root, fail, config, appDir, pageExtensions, next } = context;
|
|
10
|
+
const { APP_DIR_ALIAS, IncompatibleParallelRouteSlotsError, MissingCanonicalInterceptionRoutesError, UnmatchedAppPagesError, compareAppPaths, discoverRoutes, findMissingCanonicalInterceptionRoutes, getAppPageStaticInfo, isAppPageRoute, isAppRouteRoute, normalizeAppPath, normalizeCatchAllRoutes, selectAppPageEntry } = next;
|
|
11
|
+
const discovered = await discoverRoutes({
|
|
12
|
+
appDir,
|
|
13
|
+
pagesDir: void 0,
|
|
14
|
+
pageExtensions,
|
|
15
|
+
isDev: false,
|
|
16
|
+
baseDir: root,
|
|
17
|
+
isSrcDir: path.basename(path.dirname(appDir)) === "src"
|
|
18
|
+
});
|
|
19
|
+
const mappedAppPages = discovered.mappedAppPages ?? fail("`discoverRoutes()` returns no `mappedAppPages`");
|
|
20
|
+
const appPathsPerRoute = {};
|
|
21
|
+
for (const page of Object.keys(mappedAppPages)) (appPathsPerRoute[normalizeAppPath(page)] ??= []).push(page);
|
|
22
|
+
const { strictRouteMatching: strict } = config.experimental;
|
|
23
|
+
const strictRouteMatching = strict || void 0;
|
|
24
|
+
const { unmatchedAppPages, incompatibleParallelRouteSlots } = normalizeCatchAllRoutes(appPathsPerRoute, {
|
|
25
|
+
strictRouteMatching,
|
|
26
|
+
defaultAppPaths: Object.keys(discovered.mappedAppDefaults ?? {})
|
|
27
|
+
});
|
|
28
|
+
const pagePathsPerRoute = Object.fromEntries(Object.entries(appPathsPerRoute).flatMap(([pathname, appPaths]) => {
|
|
29
|
+
const pages = appPaths.filter(isAppPageRoute);
|
|
30
|
+
return pages.length > 0 ? [[pathname, pages]] : [];
|
|
31
|
+
}));
|
|
32
|
+
const missingCanonical = strictRouteMatching ? findMissingCanonicalInterceptionRoutes(pagePathsPerRoute) : [];
|
|
33
|
+
const routeErrors = [
|
|
34
|
+
...missingCanonical.length > 0 ? [new MissingCanonicalInterceptionRoutesError(missingCanonical)] : [],
|
|
35
|
+
...incompatibleParallelRouteSlots.length > 0 ? [new IncompatibleParallelRouteSlotsError(incompatibleParallelRouteSlots.map((slot) => ({
|
|
36
|
+
...slot,
|
|
37
|
+
layoutFile: path.relative(root, path.join(appDir, slot.layoutPath, "layout"))
|
|
38
|
+
})))] : [],
|
|
39
|
+
...unmatchedAppPages.length > 0 ? [new UnmatchedAppPagesError(unmatchedAppPages)] : []
|
|
40
|
+
];
|
|
41
|
+
if (routeErrors.length > 0) throw new Error(`vitest-plugin-rsc: \`next build\` fails on the routes of this app.\n\n` + routeErrors.map((error) => error.message).join("\n\n"));
|
|
42
|
+
const fromRoot = (file) => path.relative(root, file).split(path.sep).join("/");
|
|
43
|
+
const isRouteHandler = isAppRouteRoute;
|
|
44
|
+
const routes = [];
|
|
45
|
+
for (const [pathname, appPaths] of Object.entries(appPathsPerRoute)) {
|
|
46
|
+
appPaths.sort(compareAppPaths);
|
|
47
|
+
const page = selectAppPageEntry(pathname, appPaths);
|
|
48
|
+
const pagePath = mappedAppPages[page];
|
|
49
|
+
const own = appPaths.filter((appPath) => normalizeAppPath(appPath) === pathname);
|
|
50
|
+
const pages = own.filter((appPath) => appPath.endsWith("/page"));
|
|
51
|
+
const handlers = own.filter(isRouteHandler);
|
|
52
|
+
if (pages.length > 0 && handlers.length > 0) throw new Error(`vitest-plugin-rsc: ${pathname} is both a page and a route handler (${pages.join(", ")} and ${handlers.join(", ")}). A path can only be one of them.`);
|
|
53
|
+
const kind = page.endsWith("/page") ? "page" : isRouteHandler(page) ? "route" : void 0;
|
|
54
|
+
if (kind) routes.push({
|
|
55
|
+
kind,
|
|
56
|
+
page,
|
|
57
|
+
pathname,
|
|
58
|
+
appPaths,
|
|
59
|
+
pagePath
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const routeFile = (route) => route.pagePath.startsWith(APP_DIR_ALIAS) ? path.join(appDir, route.pagePath.slice(APP_DIR_ALIAS.length)) : void 0;
|
|
63
|
+
for (const route of routes) {
|
|
64
|
+
const file = routeFile(route);
|
|
65
|
+
if (route.kind !== "page" || !file) continue;
|
|
66
|
+
const hasLayout = (directory) => pageExtensions.some((extension) => fs.existsSync(path.join(directory, `layout.${extension}`))) || directory !== appDir && hasLayout(path.dirname(directory));
|
|
67
|
+
if (!hasLayout(path.dirname(file))) throw new Error(`vitest-plugin-rsc: ${fromRoot(file)} does not have a root layout: there is no layout file in its directory or in one above it, up to the app directory. \`next build\` stops at that too.`);
|
|
68
|
+
}
|
|
69
|
+
const edgeRouteFiles = [];
|
|
70
|
+
for (const route of routes) {
|
|
71
|
+
const file = routeFile(route);
|
|
72
|
+
if (!file) continue;
|
|
73
|
+
const { runtime } = await getAppPageStaticInfo({
|
|
74
|
+
pageFilePath: file,
|
|
75
|
+
nextConfig: config,
|
|
76
|
+
isDev: false,
|
|
77
|
+
page: route.page,
|
|
78
|
+
pageType: "app"
|
|
79
|
+
});
|
|
80
|
+
if (runtime === "edge") edgeRouteFiles.push(fromRoot(file));
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
routes,
|
|
84
|
+
/** The pages of each route, by its pathname: a route can have a page in each of its slots. */
|
|
85
|
+
appPathsPerRoute,
|
|
86
|
+
strictRouteMatching,
|
|
87
|
+
edgeRouteFiles,
|
|
88
|
+
routeFile
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { discoverAppRoutes };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Header } from "../compat/types.js";
|
|
2
|
+
import { AppRoutes } from "./routes.js";
|
|
3
|
+
import { NextContext } from "./context.js";
|
|
4
|
+
import { NextRouting } from "../project.js";
|
|
5
|
+
import * as _$next_dist_build_index0 from "next/dist/build/index.js";
|
|
6
|
+
import * as _$next_dist_build_analysis_get_page_static_info0 from "next/dist/build/analysis/get-page-static-info.js";
|
|
7
|
+
|
|
8
|
+
//#region src/project/routing.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* A rule of `headers()` in `next.config` with its `Set-Cookie` headers as
|
|
11
|
+
* one, joined with a comma. Next's build hands an adapter the headers of a
|
|
12
|
+
* rule as an object, one value per name, and `@next/routing` sets each on
|
|
13
|
+
* the response: the second cookie would be lost. `next start` sends every
|
|
14
|
+
* one. Joined, they are the way Next's build writes the `set-cookie` of a
|
|
15
|
+
* prerendered route, and `resolve()` in server-in-front.ts splits them
|
|
16
|
+
* again, with the split Next's `toNodeOutgoingHttpHeaders()` has for them.
|
|
17
|
+
*/
|
|
18
|
+
declare function joinSetCookies<T extends Pick<Header, "headers">>(rule: T): T;
|
|
19
|
+
/** The middleware of the app, as `findMiddleware()` finds it. */
|
|
20
|
+
type FoundMiddleware = Awaited<ReturnType<typeof findMiddleware>>;
|
|
21
|
+
declare function findMiddleware(context: NextContext): Promise<{
|
|
22
|
+
middlewareFile: string | undefined;
|
|
23
|
+
middleware: {
|
|
24
|
+
page: string;
|
|
25
|
+
pagePath: string;
|
|
26
|
+
staticInfo: Omit<_$next_dist_build_analysis_get_page_static_info0.PageStaticInfo, "config">;
|
|
27
|
+
} | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
/** `adapterPath` is the file of the adapter that Next's build hands the routes to: adapter.ts. */
|
|
30
|
+
declare function buildRouting(context: NextContext, {
|
|
31
|
+
routes,
|
|
32
|
+
appPathsPerRoute
|
|
33
|
+
}: AppRoutes, {
|
|
34
|
+
middleware
|
|
35
|
+
}: FoundMiddleware, adapterPath: string): Promise<{
|
|
36
|
+
routing: NextRouting;
|
|
37
|
+
routesManifest: _$next_dist_build_index0.RoutesManifest;
|
|
38
|
+
unmatchedRoutes: string[];
|
|
39
|
+
pageOf: (url: URL, {
|
|
40
|
+
proxy
|
|
41
|
+
}: {
|
|
42
|
+
proxy: boolean;
|
|
43
|
+
}) => Promise<string | undefined>;
|
|
44
|
+
}>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { FoundMiddleware, buildRouting, findMiddleware, joinSetCookies };
|