Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/runtime/internal/vite/dev-worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,24 @@ class ViteEnvRunner {
}
}

// Errors are intentionally not caught here: like production services,
// they propagate to the caller (the nitro app's error handler or the
// env-runner fetch boundary below).
async fetch(req, init) {
if (this.entryError) {
return renderError(req, this.entryError);
}
for (let i = 0; i < 5 && !(this.entry || this.entryError); i++) {
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
}
if (this.entryError) {
return renderError(req, this.entryError);
throw this.entryError;
}
if (!this.entry) {
throw httpError(503, `Vite environment "${this.name}" is unavailable`);
}
try {
const entryFetch = this.entry.fetch || this.entry.default?.fetch;
if (!entryFetch) {
throw httpError(500, `No fetch handler exported from ${this.entryPath}`);
}
return await entryFetch(req, init);
} catch (error) {
return renderError(req, error);
const entryFetch = this.entry.fetch || this.entry.default?.fetch;
if (!entryFetch) {
throw httpError(500, `No fetch handler exported from ${this.entryPath}`);
}
return entryFetch(req, init);
}
}

Expand Down Expand Up @@ -148,13 +144,17 @@ globalThis.__transform_html__ = async function (html) {

// ----- Exports (env-runner AppEntry) -----

export function fetch(req) {
export async function fetch(req) {
const viteEnv = req?.headers.get("x-vite-env") || "nitro";
const env = envs[viteEnv];
if (!env) {
return renderError(req, httpError(500, `Unknown vite environment "${viteEnv}"`));
}
return env.fetch(req);
try {
return await env.fetch(req);
} catch (error) {
return renderError(req, error);
}
}

export function upgrade(context) {
Expand Down
6 changes: 5 additions & 1 deletion test/vite/app-fixture/app/entry-server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { HTTPError } from "h3";
import { useStorage } from "nitro/storage";
import { useRuntimeConfig } from "nitro/runtime-config";

export default {
async fetch() {
async fetch(req: Request) {
if (req.url.includes("?error")) {
throw new HTTPError({ status: 418, headers: { "x-test": "123" } });
}
const storage = useStorage();
const config = useRuntimeConfig();
await storage.set("test:key", "value-from-ssr");
Expand Down
11 changes: 11 additions & 0 deletions test/vite/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ describe("vite:app", () => {
expect(res.status).not.toBe(200);
});

// HTTPError thrown from the SSR entry must propagate to the nitro app so the h3
// error handler preserves its status and headers (consistent with production).
test("propagates HTTPError status and headers from the SSR entry", async () => {
const res = await fetch(`${serverURL}/?error`, {
headers: { "sec-fetch-dest": "document", accept: "text/html" },
redirect: "manual",
});
expect(res.status).toBe(418);
expect(res.headers.get("x-test")).toBe("123");
});

// A page navigation matching only the SSR `/**` catch-all must reach the renderer.
test("routes page navigations to the SSR catch-all renderer", async () => {
const res = await fetch(`${serverURL}/some/nested/page`, {
Expand Down
Loading