@nuxt/vite-server-nightly 0.0.0 → 4.5.2-2610052030-052d82e

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.
@@ -0,0 +1,92 @@
1
+ import process from "node:process";
2
+ import { ERROR_CHANNEL_ENV, clearErrorReport, createDevErrorReporter, createErrorReport, serializeErrorCause, setErrorChannelForwarding, useErrorChannel } from "nuxt/internal/dev-error";
3
+ import { isLoopbackAddress } from "nuxt/internal/dev/peer";
4
+ //#region src/runtime/dev-error.ts
5
+ let context;
6
+ /** Whether a dev server in front owns the channel. */
7
+ const forwarding = () => !!process.env[ERROR_CHANNEL_ENV];
8
+ setErrorChannelForwarding(forwarding);
9
+ /** Install the dev server the app renders from. */
10
+ function setDevErrorContext(next) {
11
+ context = next;
12
+ useErrorChannel().catch(() => {});
13
+ }
14
+ /** Whether `pathname` is a live channel route this process serves. */
15
+ function isErrorChannelRequest(pathname) {
16
+ if (!context || forwarding()) return false;
17
+ const base = context.channel.replace(/\/$/, "");
18
+ return pathname === base || pathname.startsWith(`${base}/`);
19
+ }
20
+ /**
21
+ * Serve the live error channel: the SSE stream, report lookups and "open in editor".
22
+ *
23
+ * Trust follows the socket, since a request's origin headers are forgeable over a direct
24
+ * connection.
25
+ */
26
+ async function fetchErrorChannel(request) {
27
+ return await (await useErrorChannel()).fetchHandler(request, { trusted: isLoopbackAddress(request.ip) }) ?? new Response("Not Found", { status: 404 });
28
+ }
29
+ /**
30
+ * Build a report from the stack as raised, publish it, then rewrite the stack in place so
31
+ * every later consumer sees source positions.
32
+ */
33
+ function observeDevError(error, request, observe) {
34
+ if (!context) return Promise.resolve(void 0);
35
+ return reporter(error, request, observe);
36
+ }
37
+ const reporter = createDevErrorReporter({
38
+ get cwd() {
39
+ return context.cwd;
40
+ },
41
+ channel: channelPath,
42
+ createReport: (error) => buildReport(error),
43
+ requestInfo: (request) => ({
44
+ method: request.method,
45
+ url: new URL(request.url),
46
+ headers: request.headers
47
+ }),
48
+ mapStack: (error) => fixStacktraces(error, context.server)
49
+ });
50
+ async function buildReport(error) {
51
+ const { viteLoader } = await import("my-bad/vite");
52
+ return createErrorReport(error, {
53
+ cwd: context.cwd,
54
+ loaders: [viteLoader(ssrOnly(context.server), { fs: false })]
55
+ });
56
+ }
57
+ /**
58
+ * The dev server with only its SSR environment visible, since `viteLoader` maps through
59
+ * the first environment holding the file and an SSR frame belongs to the SSR transform.
60
+ */
61
+ function ssrOnly(server) {
62
+ return Object.create(server, { environments: { value: { ssr: server.environments.ssr } } });
63
+ }
64
+ /**
65
+ * Ask Vite to map the stacks of an error and its causes. It maps in place and appends to
66
+ * `message` on a stack it has already rewritten, so each one is mapped on a carrier.
67
+ */
68
+ function fixStacktraces(error, server, seen = /* @__PURE__ */ new Set()) {
69
+ if (!(error instanceof Error) || seen.has(error)) return;
70
+ seen.add(error);
71
+ if (typeof error.stack === "string") {
72
+ const carrier = { stack: error.stack };
73
+ server.ssrFixStacktrace(carrier);
74
+ if (carrier.stack) try {
75
+ Object.defineProperty(error, "stack", {
76
+ value: carrier.stack,
77
+ writable: true,
78
+ configurable: true
79
+ });
80
+ } catch {}
81
+ }
82
+ fixStacktraces(error.cause, server, seen);
83
+ }
84
+ /** The error's `cause` chain, as the error page receives it in the payload. */
85
+ function errorCause(error) {
86
+ return serializeErrorCause(error?.cause);
87
+ }
88
+ function channelPath() {
89
+ return process.env[ERROR_CHANNEL_ENV] || context.channel;
90
+ }
91
+ //#endregion
92
+ export { clearErrorReport, errorCause, fetchErrorChannel, isErrorChannelRequest, observeDevError, setDevErrorContext };
@@ -0,0 +1,9 @@
1
+ //#region src/runtime/dev-handler.ts
2
+ /** Stands in for the render in development, where the dev server serves the app. */
3
+ const fetch = () => Promise.resolve(new Response("The Nuxt server handler is only emitted by a build. In development the app is served by the Nuxt dev server.", {
4
+ status: 503,
5
+ headers: { "content-type": "text/plain;charset=utf-8" }
6
+ }));
7
+ var dev_handler_default = { fetch };
8
+ //#endregion
9
+ export { dev_handler_default as default, fetch };
@@ -0,0 +1,16 @@
1
+ //#region src/runtime/event.ts
2
+ /**
3
+ * The request event the renderer reads, described in web standards only: a platform
4
+ * `Request`, its URL, a mutable response to influence, and a context the app layer stores
5
+ * per-request state in.
6
+ */
7
+ function createRequestEvent(request) {
8
+ return {
9
+ req: request,
10
+ url: new URL(request.url),
11
+ res: { headers: new Headers() },
12
+ context: {}
13
+ };
14
+ }
15
+ //#endregion
16
+ export { createRequestEvent };
@@ -0,0 +1,51 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { readdir } from "node:fs/promises";
3
+ import { join, relative, sep } from "node:path";
4
+ import { serve } from "srvx/node";
5
+ import { staticMiddleware } from "srvx/static";
6
+ //#region src/runtime/node.ts
7
+ /**
8
+ * Serve the build's static assets, and hand everything else to the renderer.
9
+ *
10
+ * The static handler comes first so a request for a hashed asset never reaches a render.
11
+ */
12
+ function createNodeServer(options) {
13
+ const dir = typeof options.publicDir === "string" ? options.publicDir : fileURLToPath(options.publicDir);
14
+ const serveStatic = staticMiddleware({ dir });
15
+ const publicFiles = listFiles(dir);
16
+ return { fetch: async (request) => {
17
+ const files = await publicFiles;
18
+ if (!files || mayBeStaticFile(files, request)) return serveStatic(request, () => options.fetch(request));
19
+ return options.fetch(request);
20
+ } };
21
+ }
22
+ async function listFiles(dir) {
23
+ try {
24
+ const entries = await readdir(dir, {
25
+ recursive: true,
26
+ withFileTypes: true
27
+ });
28
+ const files = /* @__PURE__ */ new Set();
29
+ for (const entry of entries) if (entry.isFile()) files.add("/" + relative(dir, join(entry.parentPath, entry.name)).split(sep).join("/"));
30
+ return files;
31
+ } catch {
32
+ return;
33
+ }
34
+ }
35
+ /** Whether the request path, or one of the variants the static handler tries, is a public file. */
36
+ function mayBeStaticFile(files, request) {
37
+ let path;
38
+ try {
39
+ path = decodeURIComponent(new URL(request.url).pathname);
40
+ } catch {
41
+ return true;
42
+ }
43
+ if (path.endsWith("/")) return files.has(path + "index.html");
44
+ return files.has(path) || files.has(path + ".html") || files.has(path + "/index.html");
45
+ }
46
+ /** Listen for requests, using the port and host the platform environment configures. */
47
+ function listen(server) {
48
+ serve({ fetch: server.fetch });
49
+ }
50
+ //#endregion
51
+ export { createNodeServer, listen };
@@ -0,0 +1,58 @@
1
+ import { NuxtServerError } from "./renderer.mjs";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ //#region src/runtime/prerender.ts
4
+ /**
5
+ * The capabilities the renderer only has while prerendering. Everything is held in memory:
6
+ * the crawler drives the handler in-process, so nothing outlives the build.
7
+ */
8
+ function createPrerenderOptions(options = {}) {
9
+ const payloads = /* @__PURE__ */ new Map();
10
+ return {
11
+ payloadCache: {
12
+ hasItem: (key) => payloads.has(key),
13
+ getItem: (key) => payloads.get(key),
14
+ setItem: (key, value) => void payloads.set(key, value)
15
+ },
16
+ sharedDataCache: options.sharedData === false ? void 0 : createSharedDataCache(),
17
+ wrapRender: (event, render) => {
18
+ const renderingURL = event.url.pathname + event.url.search;
19
+ const stack = renderingURLs.getStore();
20
+ if (stack?.includes(renderingURL)) {
21
+ const chain = [...stack, renderingURL].filter((url) => !url.startsWith("/__nuxt_error")).map((url) => `"${url}"`).join(" -> ");
22
+ throw new NuxtServerError({
23
+ status: 508,
24
+ statusText: `Loop detected while prerendering "${renderingURL}" (${chain}). Check for \`useFetch\`/\`$fetch\` calls targeting a URL that is currently being rendered.`
25
+ });
26
+ }
27
+ return renderingURLs.run([...stack || [], renderingURL], render);
28
+ }
29
+ };
30
+ }
31
+ /** URLs rendering in the active async context, oldest first. A repeat entry is a cycle. */
32
+ const renderingURLs = new AsyncLocalStorage();
33
+ function createSharedDataCache() {
34
+ const resolved = /* @__PURE__ */ new Map();
35
+ const pending = /* @__PURE__ */ new Map();
36
+ const chains = /* @__PURE__ */ new Map();
37
+ return {
38
+ get(key) {
39
+ const chain = renderingURLs.getStore();
40
+ const setChain = chains.get(key);
41
+ if (chain?.length && setChain?.length && setChain.some((url) => chain.includes(url))) return;
42
+ return pending.get(key) ?? resolved.get(key);
43
+ },
44
+ async set(key, value) {
45
+ pending.set(key, value);
46
+ const chain = renderingURLs.getStore();
47
+ if (chain?.length) chains.set(key, chain);
48
+ try {
49
+ resolved.set(key, Promise.resolve(await value));
50
+ } catch {} finally {
51
+ pending.delete(key);
52
+ chains.delete(key);
53
+ }
54
+ }
55
+ };
56
+ }
57
+ //#endregion
58
+ export { createPrerenderOptions };
@@ -0,0 +1,220 @@
1
+ import { createRequestEvent } from "./event.mjs";
2
+ import { joinURL, withQuery } from "ufo";
3
+ import { appendVary, describeError, isExpectedError, isJsonRequest } from "nuxt/internal/renderer/error";
4
+ import { mergeHeaders } from "nuxt/internal/renderer/headers";
5
+ import { useServerHooks } from "nuxt/server";
6
+ import { buildAssetsURL, publicAssetsURL } from "#internal/nuxt/paths";
7
+ //#region src/runtime/renderer.ts
8
+ /** An error carrying the HTTP status the renderer refused a request with. */
9
+ var NuxtServerError = class extends Error {
10
+ status;
11
+ statusText;
12
+ data;
13
+ headers;
14
+ constructor(init) {
15
+ super(init.statusText || `Request failed with status ${init.status}`);
16
+ this.name = "NuxtServerError";
17
+ this.status = init.status;
18
+ this.statusText = init.statusText || "";
19
+ this.data = init.data;
20
+ this.headers = init.headers;
21
+ }
22
+ };
23
+ /**
24
+ * The capabilities `@nuxt/vite-server` provides to the renderer. Everything comes from the
25
+ * platform or from values the build serialised, so the same options run on a node server
26
+ * and in a web-standard worker.
27
+ *
28
+ * Route rules come from the matcher the build compiled, so a route is server-rendered
29
+ * unless a rule says otherwise.
30
+ */
31
+ function createRendererOptions(runtimeConfig, matchRouteRules, prerender) {
32
+ globalThis.__buildAssetsURL = buildAssetsURL;
33
+ globalThis.__publicAssetsURL = publicAssetsURL;
34
+ return {
35
+ runtimeConfig,
36
+ buildAssetsURL,
37
+ publicAssetsURL,
38
+ getRouteRules: (event) => ({
39
+ ssr: true,
40
+ ...matchRouteRules(event.url.pathname)
41
+ }),
42
+ hooks: useServerHooks,
43
+ createResponse: (body, init) => new Response(body, init),
44
+ createError: (init) => new NuxtServerError(init),
45
+ prerender,
46
+ onRenderSuccess: import.meta.dev ? () => {
47
+ import("./dev-error.mjs").then(({ clearErrorReport }) => clearErrorReport()).catch(() => {});
48
+ } : void 0,
49
+ captureError: (error) => {
50
+ if (!import.meta.dev) console.error(error);
51
+ },
52
+ onDevError: import.meta.dev ? (error, event, options) => import("./dev-error.mjs").then(({ observeDevError }) => observeDevError(error, event.req, options)) : void 0
53
+ };
54
+ }
55
+ /**
56
+ * Header the crawler reads additional routes from. The renderer collects them on the
57
+ * request event, which does not cross the handler boundary, so they ride the response.
58
+ */
59
+ const PRERENDER_HINTS_HEADER = "x-nuxt-prerender";
60
+ /**
61
+ * A web-standard handler over the renderer: it renders the request, and renders the app's
62
+ * error page for a request the render refused. In development it also serves the live
63
+ * error channel and publishes what it failed on to it.
64
+ */
65
+ function createFetchHandler(renderer, matchRouteRules) {
66
+ return async function fetch(request) {
67
+ if (import.meta.dev) {
68
+ const devErrors = await import("./dev-error.mjs");
69
+ if (devErrors.isErrorChannelRequest(new URL(request.url).pathname)) return devErrors.fetchErrorChannel(request);
70
+ }
71
+ const event = createRequestEvent(request);
72
+ const rules = matchRouteRules(event.url.pathname);
73
+ if (rules.redirect) return redirectResponse(event, rules.redirect, rules.headers);
74
+ try {
75
+ const response = await renderer.fetch(event);
76
+ applyHeaders(response, rules.headers);
77
+ if (import.meta.prerender) applyPrerenderHints(event, response);
78
+ return response;
79
+ } catch (error) {
80
+ const response = await renderError(renderer, request, error, event);
81
+ applyHeaders(response, rules.headers);
82
+ if (import.meta.prerender) applyPrerenderHints(event, response);
83
+ return response;
84
+ }
85
+ };
86
+ }
87
+ function applyHeaders(response, headers) {
88
+ for (const name in headers) response.headers.set(name, headers[name]);
89
+ }
90
+ /**
91
+ * Answer a `redirect` rule. A target carrying `**` moves a whole subtree: the tail of the
92
+ * request past the prefix the rule matched under is interpolated into it. Targets naming a
93
+ * parameter of the matched pattern are not resolved; those need a server runtime.
94
+ */
95
+ function redirectResponse(event, redirect, headers) {
96
+ let location = redirect.to;
97
+ if (location.includes("**")) {
98
+ const path = event.url.pathname;
99
+ const tail = redirect.base && path.startsWith(redirect.base) ? path.slice(redirect.base.length) : path;
100
+ location = location.endsWith("/**") ? joinURL(location.slice(0, -3), tail) : location.replace("**", tail.replace(/^\//, ""));
101
+ }
102
+ const target = appendSearch(location, event.url.search);
103
+ const response = new Response(redirectBody(target), {
104
+ status: redirect.status ?? 307,
105
+ headers: {
106
+ "location": target,
107
+ "content-type": "text/html"
108
+ }
109
+ });
110
+ applyHeaders(response, headers);
111
+ return response;
112
+ }
113
+ /** A meta-refresh document, so a prerendered redirect is followed when served as a static file. */
114
+ function redirectBody(location) {
115
+ return `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${location.replace(REDIRECT_UNSAFE_RE, (char) => REDIRECT_ESCAPES[char])}"></head></html>`;
116
+ }
117
+ const REDIRECT_ESCAPES = {
118
+ "\"": "%22",
119
+ "'": "%27",
120
+ "<": "%3C",
121
+ ">": "%3E",
122
+ "&": "%26"
123
+ };
124
+ const REDIRECT_UNSAFE_RE = /["'<>&]/g;
125
+ /** Carry the request's query onto a redirect target, ahead of any fragment the target names. */
126
+ function appendSearch(target, search) {
127
+ if (!search) return target;
128
+ const hashIndex = target.indexOf("#");
129
+ const path = hashIndex === -1 ? target : target.slice(0, hashIndex);
130
+ const hash = hashIndex === -1 ? "" : target.slice(hashIndex);
131
+ return path + (!path.includes("?") ? "?" : path.endsWith("?") || path.endsWith("&") ? "" : "&") + search.slice(1) + hash;
132
+ }
133
+ function applyPrerenderHints(event, response) {
134
+ const paths = event.context.nuxt?.prerenderRoutes;
135
+ if (!paths?.length) return;
136
+ response.headers.append(PRERENDER_HINTS_HEADER, paths.map((path) => encodeURIComponent(path)).join(", "));
137
+ }
138
+ async function renderError(renderer, request, error, event) {
139
+ const described = describeError(error);
140
+ const { status, statusText, message } = described;
141
+ const url = new URL(request.url);
142
+ const devErrors = import.meta.dev ? await import("./dev-error.mjs") : void 0;
143
+ const report = devErrors ? await devErrors.observeDevError(error, request, { expected: isExpectedError(error, described) }).catch(() => void 0) : void 0;
144
+ if (isJsonRequest(request, url.pathname)) {
145
+ const body = {
146
+ error: true,
147
+ status,
148
+ statusText,
149
+ message,
150
+ data: described.data,
151
+ ...import.meta.dev && { stack: error?.stack?.split("\n").map((line) => line.trim()) }
152
+ };
153
+ return new Response(JSON.stringify(body), {
154
+ status,
155
+ statusText,
156
+ headers: errorResponseHeaders(event, described, "application/json;charset=utf-8")
157
+ });
158
+ }
159
+ const data = error?.data;
160
+ const stack = error?.stack;
161
+ const errorEvent = createRequestEvent(new Request(withQuery(new URL("/__nuxt_error", url).href, {
162
+ status: String(status),
163
+ statusCode: String(status),
164
+ statusText,
165
+ statusMessage: statusText,
166
+ message,
167
+ url: request.url,
168
+ ...data === void 0 ? {} : { data: typeof data === "string" ? data : JSON.stringify(data) },
169
+ ...import.meta.dev && stack ? { stack } : {}
170
+ }), { headers: request.headers }));
171
+ const state = (import.meta.prerender ? event.context.nuxt : void 0) || {};
172
+ state["~rendering-error"] = true;
173
+ if (devErrors) {
174
+ const cause = devErrors.errorCause(error);
175
+ if (cause !== void 0) state["~error-cause"] = cause;
176
+ }
177
+ errorEvent.context.nuxt = state;
178
+ if (import.meta.prerender) event.context.nuxt = state;
179
+ const rendered = await renderer.fetch(errorEvent).catch(() => null);
180
+ if (rendered) {
181
+ const responseHeaders = errorResponseHeaders(event, described, "text/html;charset=utf-8", rendered.headers);
182
+ if (report && !import.meta.test) {
183
+ const html = await rendered.text();
184
+ const body = await report.overlay(html).catch(() => html);
185
+ return new Response(body, {
186
+ status,
187
+ statusText,
188
+ headers: responseHeaders
189
+ });
190
+ }
191
+ return new Response(rendered.body, {
192
+ status,
193
+ statusText,
194
+ headers: responseHeaders
195
+ });
196
+ }
197
+ if (report) {
198
+ const page = await report.page().catch(() => void 0);
199
+ if (page) return new Response(page, {
200
+ status,
201
+ statusText,
202
+ headers: errorResponseHeaders(event, described, "text/html;charset=utf-8")
203
+ });
204
+ }
205
+ return new Response(message, {
206
+ status,
207
+ statusText,
208
+ headers: errorResponseHeaders(event, described, "text/plain;charset=utf-8")
209
+ });
210
+ }
211
+ function errorResponseHeaders(event, described, contentType, page) {
212
+ const headers = new Headers(event.res.headers);
213
+ if (page) mergeHeaders(headers, page);
214
+ mergeHeaders(headers, described.headers);
215
+ headers.set("content-type", contentType);
216
+ appendVary(headers, "accept, sec-fetch-mode");
217
+ return headers;
218
+ }
219
+ //#endregion
220
+ export { NuxtServerError, createFetchHandler, createRendererOptions };
@@ -0,0 +1,15 @@
1
+ import { joinURL } from "ufo";
2
+ import { resolveServerFetchInit } from "nuxt/internal/server-default";
3
+ //#region src/runtime/server.ts
4
+ /** The `nuxt/server` helpers backed by the build's route rules and app handler. */
5
+ function createServerHelpers(match, loadFetch, baseURL) {
6
+ return {
7
+ getRouteRules: (event) => match(event.url.pathname),
8
+ matchRouteRules: (path, _method) => match(joinURL(baseURL, path)),
9
+ serverFetch: async (event, path, init) => {
10
+ return (await loadFetch())(new Request(new URL(joinURL(baseURL, path), event.req.url), resolveServerFetchInit(event, init)));
11
+ }
12
+ };
13
+ }
14
+ //#endregion
15
+ export { createServerHelpers };
package/package.json CHANGED
@@ -1,8 +1,52 @@
1
1
  {
2
2
  "name": "@nuxt/vite-server-nightly",
3
- "version": "0.0.0",
3
+ "version": "4.5.2-2610052030-052d82e",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/nuxt/nuxt.git",
7
+ "directory": "packages/vite-server"
8
+ },
9
+ "description": "Experimental pure Vite server builder for Nuxt",
10
+ "homepage": "https://nuxt.com",
11
+ "license": "MIT",
12
+ "type": "module",
13
+ "types": "./dist/index.d.mts",
14
+ "exports": {
15
+ ".": "./dist/index.mjs",
16
+ "./package.json": "./package.json"
17
+ },
4
18
  "files": [
5
19
  "dist"
6
20
  ],
7
- "author": "Daniel Roe <daniel@roe.dev>"
8
- }
21
+ "dependencies": {
22
+ "@nuxt/kit": "npm:@nuxt/kit-nightly@4.5.2-2610052030-052d82e",
23
+ "clickable-path": "^0.1.1",
24
+ "defu": "^6.1.7",
25
+ "exsolve": "^1.1.3",
26
+ "mocked-exports": "^0.1.1",
27
+ "my-bad": "^0.2.10",
28
+ "pathe": "^2.0.3",
29
+ "rou3": "^0.9.2",
30
+ "srvx": "1.0.5",
31
+ "ufo": "^1.6.4",
32
+ "ultrahtml": "^1.7.0"
33
+ },
34
+ "devDependencies": {
35
+ "@nuxt/schema": "npm:@nuxt/schema-nightly@4.5.2-2610052030-052d82e",
36
+ "nuxt": "npm:nuxt-nightly@4.5.2-2610052030-052d82e",
37
+ "tsdown": "0.23.0",
38
+ "vite": "^8.3.1",
39
+ "vitest": "5.0.3"
40
+ },
41
+ "peerDependencies": {
42
+ "nuxt": "npm:nuxt-nightly@4.5.2-2610052030-052d82e",
43
+ "vite": "^8.3.1"
44
+ },
45
+ "engines": {
46
+ "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "test:attw": "node ../../scripts/test-attw.ts"
51
+ }
52
+ }
package/dist/.gitkeep DELETED
File without changes