Skip to content

Commit dda449a

Browse files
fix(remix-dev/vite, server-runtime): pass Vite server errors to vite.ssrFixStacktrace (#8066)
Co-authored-by: Mark Dalgleish <[email protected]>
1 parent 3bf45f0 commit dda449a

File tree

5 files changed

+80
-5
lines changed

5 files changed

+80
-5
lines changed

.changeset/mean-knives-beam.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@remix-run/dev": patch
3+
"@remix-run/server-runtime": patch
4+
---
5+
6+
Pass request handler errors to `vite.ssrFixStacktrace` in Vite dev to ensure stack traces correctly map to the original source code

integration/vite-dev-test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,39 @@ test.describe("Vite dev", () => {
197197
</>
198198
}
199199
`,
200+
"app/routes/error-stacktrace.tsx": js`
201+
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
202+
import { Link, useLocation } from "@remix-run/react";
203+
204+
export const loader: LoaderFunction = ({ request }) => {
205+
if (request.url.includes("crash-loader")) {
206+
throw new Error("crash-loader");
207+
}
208+
return null;
209+
};
210+
211+
export default function TestRoute() {
212+
const location = useLocation();
213+
214+
if (import.meta.env.SSR && location.search.includes("crash-server-render")) {
215+
throw new Error("crash-server-render");
216+
}
217+
218+
return (
219+
<div>
220+
<ul>
221+
{["crash-loader", "crash-server-render"].map(
222+
(v) => (
223+
<li key={v}>
224+
<Link to={"/?" + v}>{v}</Link>
225+
</li>
226+
)
227+
)}
228+
</ul>
229+
</div>
230+
);
231+
}
232+
`,
200233
"app/routes/known-route-exports.tsx": js`
201234
import { useMatches } from "@remix-run/react";
202235
@@ -385,6 +418,29 @@ test.describe("Vite dev", () => {
385418
expect(pageErrors).toEqual([]);
386419
});
387420

421+
test("request errors map to original source code", async ({ page }) => {
422+
let pageErrors: unknown[] = [];
423+
page.on("pageerror", (error) => pageErrors.push(error));
424+
425+
await page.goto(
426+
`http://localhost:${devPort}/error-stacktrace?crash-server-render`
427+
);
428+
await expect(page.locator("main")).toContainText(
429+
"Error: crash-server-render"
430+
);
431+
await expect(page.locator("main")).toContainText(
432+
"error-stacktrace.tsx:16:11"
433+
);
434+
435+
await page.goto(
436+
`http://localhost:${devPort}/error-stacktrace?crash-loader`
437+
);
438+
await expect(page.locator("main")).toContainText("Error: crash-loader");
439+
await expect(page.locator("main")).toContainText(
440+
"error-stacktrace.tsx:7:11"
441+
);
442+
});
443+
388444
test("handle known route exports with HMR", async ({ page }) => {
389445
let pageErrors: unknown[] = [];
390446
page.on("pageerror", (error) => pageErrors.push(error));

packages/remix-dev/vite/plugin.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,9 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
669669
setTimeout(showUnstableWarning, 50);
670670
});
671671

672-
// Give the request handler access to the critical CSS in dev to avoid a
673-
// flash of unstyled content since Vite injects CSS file contents via JS
674672
setDevServerHooks({
673+
// Give the request handler access to the critical CSS in dev to avoid a
674+
// flash of unstyled content since Vite injects CSS file contents via JS
675675
getCriticalCss: async (build, url) => {
676676
invariant(cachedPluginConfig);
677677
return getStylesForUrl(
@@ -682,6 +682,13 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
682682
url
683683
);
684684
},
685+
// If an error is caught within the request handler, let Vite fix the
686+
// stack trace so it maps back to the actual source code
687+
processRequestError: (error) => {
688+
if (error instanceof Error) {
689+
vite.ssrFixStacktrace(error);
690+
}
691+
},
685692
});
686693

687694
// We cache the pluginConfig here to make sure we're only invalidating virtual modules when necessary.

packages/remix-server-runtime/dev.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ export function logDevReady(build: ServerBuild) {
2727
}
2828

2929
type DevServerHooks = {
30-
getCriticalCss: (
30+
getCriticalCss?: (
3131
build: ServerBuild,
3232
pathname: string
3333
) => Promise<string | undefined>;
34+
processRequestError?: (error: unknown) => void;
3435
};
3536

3637
const globalDevServerHooksKey = "__remix_devServerHooks";

packages/remix-server-runtime/server.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,17 @@ export const createRequestHandler: CreateRequestHandlerFunction = (
9494
let url = new URL(request.url);
9595

9696
let matches = matchServerRoutes(routes, url.pathname);
97-
let handleError = (error: unknown) =>
97+
let handleError = (error: unknown) => {
98+
if (mode === ServerMode.Development) {
99+
getDevServerHooks()?.processRequestError?.(error);
100+
}
101+
98102
errorHandler(error, {
99103
context: loadContext,
100104
params: matches && matches.length > 0 ? matches[0].params : {},
101105
request,
102106
});
107+
};
103108

104109
let response: Response;
105110
if (url.searchParams.has("_data")) {
@@ -137,7 +142,7 @@ export const createRequestHandler: CreateRequestHandlerFunction = (
137142
} else {
138143
let criticalCss =
139144
mode === ServerMode.Development
140-
? await getDevServerHooks()?.getCriticalCss(_build, url.pathname)
145+
? await getDevServerHooks()?.getCriticalCss?.(_build, url.pathname)
141146
: undefined;
142147

143148
response = await handleDocumentRequestRR(

0 commit comments

Comments
 (0)