From 154e5e8c67cb3c5e25483a1a0d6a94bffcf9d9f6 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:21:43 +0900 Subject: [PATCH] feat(ssr): avoid errors when rewriting already rewritten stacktrace --- packages/vite/src/node/server/index.ts | 1 + .../node/ssr/__tests__/ssrStacktrace.spec.ts | 22 +++++++++++-- packages/vite/src/node/ssr/ssrStacktrace.ts | 32 +++++++++++++------ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 36c5317b54cfd9..bf06bd614018aa 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -648,6 +648,7 @@ export async function _createServer( "ssrRewriteStacktrace doesn't need to be used for Environment Module Runners.", ) return ssrRewriteStacktrace(stack, server.environments.ssr.moduleGraph) + .result }, async reloadModule(module) { warnFutureDeprecation(config, 'removeServerReloadModule') diff --git a/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts index f8f2f27666ffa8..422a5d077d9315 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from 'node:url' -import { test } from 'vitest' +import { expect, test } from 'vitest' import { createServer } from '../../server' const root = fileURLToPath(new URL('./', import.meta.url)) @@ -22,8 +22,26 @@ test('call rewriteStacktrace twice', async () => { for (let i = 0; i < 2; i++) { try { await server.ssrLoadModule('/fixtures/modules/has-error.js') - } catch (e) { + } catch (e: any) { server.ssrFixStacktrace(e) } } }) + +test('outputs message when stacktrace appears to be already rewritten', async () => { + const server = await createDevServer() + try { + await server.ssrLoadModule('/fixtures/modules/has-error.js') + } catch (e: any) { + // Manually craft an already-rewritten stacktrace with invalid line/column + // that will trigger the alreadyRewritten flag + e.stack = e.stack.replace( + /fixtures\/modules\/has-error\.js:\d+:\d+/, + 'fixtures/modules/has-error.js:1:1', + ) + server.ssrFixStacktrace(e) + expect(e.message).toContain( + 'The stacktrace appears to be already rewritten by something else', + ) + } +}) diff --git a/packages/vite/src/node/ssr/ssrStacktrace.ts b/packages/vite/src/node/ssr/ssrStacktrace.ts index f10b2150937d52..be1bd44874ab33 100644 --- a/packages/vite/src/node/ssr/ssrStacktrace.ts +++ b/packages/vite/src/node/ssr/ssrStacktrace.ts @@ -23,14 +23,16 @@ function calculateOffsetOnce() { export function ssrRewriteStacktrace( stack: string, moduleGraph: EnvironmentModuleGraph, -): string { +): { result: string; alreadyRewritten: boolean } { calculateOffsetOnce() - return stack + + let alreadyRewritten = false + const rewritten = stack .split('\n') .map((line) => { return line.replace( /^ {4}at (?:(\S.*?)\s\()?(.+?):(\d+)(?::(\d+))?\)?/, - (input, varName, id, line, column) => { + (input, varName, id, originalLine, originalColumn) => { if (!id) return input const mod = moduleGraph.getModuleById(id) @@ -41,13 +43,15 @@ export function ssrRewriteStacktrace( } const traced = new TraceMap(rawSourceMap as any) + const line = Number(originalLine) - offset + // stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed + const column = Number(originalColumn) - 1 + if (line <= 0 || column < 0) { + alreadyRewritten = true + return input + } - const pos = originalPositionFor(traced, { - line: Number(line) - offset, - // stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed - column: Number(column) - 1, - }) - + const pos = originalPositionFor(traced, { line, column }) if (!pos.source) { return input } @@ -65,6 +69,7 @@ export function ssrRewriteStacktrace( ) }) .join('\n') + return { result: rewritten, alreadyRewritten } } export function rebindErrorStacktrace(e: Error, stacktrace: string): void { @@ -94,8 +99,15 @@ export function ssrFixStacktrace( // stacktrace shouldn't be rewritten more than once if (rewroteStacktraces.has(e)) return - const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph) + const { result: stacktrace, alreadyRewritten } = ssrRewriteStacktrace( + e.stack, + moduleGraph, + ) rebindErrorStacktrace(e, stacktrace) + if (alreadyRewritten) { + e.message += + ' (The stacktrace appears to be already rewritten by something else, but was passed to vite.ssrFixStacktrace. This may cause incorrect stacktraces.)' + } rewroteStacktraces.add(e) }