Skip to content

Commit 88c855e

Browse files
fi3eworksapphi-red
andauthored
fix(ssr): stacktrace uses abs path with or without sourcemap (#12902)
Co-authored-by: sapphi-red <[email protected]>
1 parent feef035 commit 88c855e

File tree

7 files changed

+67
-34
lines changed

7 files changed

+67
-34
lines changed

packages/vite/src/node/ssr/ssrModuleLoader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ async function instantiateModule(
222222
ssrExportAllKey,
223223
'"use strict";' +
224224
result.code +
225-
`\n//# sourceURL=${mod.url}${sourceMapSuffix}`,
225+
`\n//# sourceURL=${mod.id}${sourceMapSuffix}`,
226226
)
227227
await initModule(
228228
context.global,

packages/vite/src/node/ssr/ssrStacktrace.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path'
12
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'
23
import type { ModuleGraph } from '../server/moduleGraph'
34

@@ -21,10 +22,10 @@ export function ssrRewriteStacktrace(
2122
.map((line) => {
2223
return line.replace(
2324
/^ {4}at (?:(\S.*?)\s\()?(.+?):(\d+)(?::(\d+))?\)?/,
24-
(input, varName, url, line, column) => {
25-
if (!url) return input
25+
(input, varName, id, line, column) => {
26+
if (!id) return input
2627

27-
const mod = moduleGraph.urlToModuleMap.get(url)
28+
const mod = moduleGraph.idToModuleMap.get(id)
2829
const rawSourceMap = mod?.ssrTransformResult?.map
2930

3031
if (!rawSourceMap) {
@@ -35,15 +36,18 @@ export function ssrRewriteStacktrace(
3536

3637
const pos = originalPositionFor(traced, {
3738
line: Number(line) - offset,
38-
column: Number(column),
39+
// stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed
40+
column: Number(column) - 1,
3941
})
4042

4143
if (!pos.source || pos.line == null || pos.column == null) {
4244
return input
4345
}
4446

4547
const trimmedVarName = varName.trim()
46-
const source = `${pos.source}:${pos.line}:${pos.column}`
48+
const sourceFile = path.resolve(path.dirname(id), pos.source)
49+
// stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed
50+
const source = `${sourceFile}:${pos.line}:${pos.column + 1}`
4751
if (!trimmedVarName || trimmedVarName === 'eval') {
4852
return ` at ${source}`
4953
} else {

packages/vite/src/node/ssr/ssrTransform.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path'
12
import MagicString from 'magic-string'
23
import type { SourceMap } from 'rollup'
34
import type {
@@ -285,7 +286,7 @@ async function ssrTransformScript(
285286
false,
286287
) as SourceMap
287288
} else {
288-
map.sources = [url]
289+
map.sources = [path.basename(url)]
289290
// needs to use originalCode instead of code
290291
// because code might be already transformed even if map is null
291292
map.sourcesContent = [originalCode]

playground/ssr-html/__tests__/ssr-html.spec.ts

+30-17
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,36 @@ describe.runIf(isServe)('hmr', () => {
6262
describe.runIf(isServe)('stacktrace', () => {
6363
const execFileAsync = promisify(execFile)
6464

65-
for (const sourcemapsEnabled of [false, true]) {
66-
test(`stacktrace is correct when sourcemaps is${
67-
sourcemapsEnabled ? '' : ' not'
68-
} enabled in Node.js`, async () => {
69-
const testStacktraceFile = path.resolve(
70-
__dirname,
71-
'../test-stacktrace.js',
72-
)
65+
for (const ext of ['js', 'ts']) {
66+
for (const sourcemapsEnabled of [false, true]) {
67+
test(`stacktrace of ${ext} is correct when sourcemaps is${
68+
sourcemapsEnabled ? '' : ' not'
69+
} enabled in Node.js`, async () => {
70+
const testStacktraceFile = path.resolve(
71+
__dirname,
72+
'../test-stacktrace.js',
73+
)
7374

74-
const p = await execFileAsync('node', [
75-
testStacktraceFile,
76-
'' + sourcemapsEnabled,
77-
])
78-
const line = p.stdout
79-
.split('\n')
80-
.find((line) => line.includes('Module.error'))
81-
expect(line.trim()).toMatch(/[\\/]src[\\/]error\.js:2:9/)
82-
})
75+
const p = await execFileAsync('node', [
76+
testStacktraceFile,
77+
'' + sourcemapsEnabled,
78+
ext,
79+
])
80+
const lines = p.stdout
81+
.split('\n')
82+
.filter((line) => line.includes('Module.error'))
83+
84+
const reg = new RegExp(
85+
path
86+
.resolve(__dirname, '../src', `error.${ext}`)
87+
.replace(/\\/g, '\\\\') + ':2:9',
88+
'i',
89+
)
90+
91+
lines.forEach((line) => {
92+
expect(line.trim()).toMatch(reg)
93+
})
94+
})
95+
}
8396
}
8497
})

playground/ssr-html/src/error.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function error() {
2+
throw new Error('e')
3+
}

playground/ssr-html/test-stacktrace.js

+19-10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { fileURLToPath } from 'node:url'
33
import { createServer } from 'vite'
44

55
const isSourceMapEnabled = process.argv[2] === 'true'
6+
const ext = process.argv[3]
67
process.setSourceMapsEnabled(isSourceMapEnabled)
78
console.log('# sourcemaps enabled:', isSourceMapEnabled)
9+
console.log('# source file extension:', ext)
810

911
const version = (() => {
1012
const m = process.version.match(/^v(\d+)\.(\d+)\.\d+$/)
@@ -31,17 +33,24 @@ const vite = await createServer({
3133
appType: 'custom',
3234
})
3335

34-
const mod = await vite.ssrLoadModule('/src/error.js')
35-
try {
36-
mod.error()
37-
} catch (e) {
38-
// this should not be called
39-
// when sourcemap support for `new Function` is supported and sourcemap is enabled
40-
// because the stacktrace is already rewritten by Node.js
41-
if (!(isSourceMapEnabled && isFunctionSourceMapSupported)) {
42-
vite.ssrFixStacktrace(e)
36+
const dir = path.dirname(fileURLToPath(import.meta.url))
37+
38+
const abs1 = await vite.ssrLoadModule(`/src/error.${ext}`)
39+
const abs2 = await vite.ssrLoadModule(path.resolve(dir, `./src/error.${ext}`))
40+
const relative = await vite.ssrLoadModule(`./src/error.${ext}`)
41+
42+
for (const mod of [abs1, abs2, relative]) {
43+
try {
44+
mod.error()
45+
} catch (e) {
46+
// this should not be called
47+
// when sourcemap support for `new Function` is supported and sourcemap is enabled
48+
// because the stacktrace is already rewritten by Node.js
49+
if (!(isSourceMapEnabled && isFunctionSourceMapSupported)) {
50+
vite.ssrFixStacktrace(e)
51+
}
52+
console.log(e)
4353
}
44-
console.log(e)
4554
}
4655

4756
await vite.close()

playground/test-utils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ export const formatSourcemapForSnapshot = (map: any): any => {
308308
delete m.file
309309
delete m.names
310310
m.sources = m.sources.map((source) => source.replace(root, '/root'))
311+
if (m.sourceRoot) {
312+
m.sourceRoot = m.sourceRoot.replace(root, '/root')
313+
}
311314
return m
312315
}
313316

0 commit comments

Comments
 (0)