Skip to content

Commit 5cee4fb

Browse files
fix: stack trace point to incorrect file (#3004) (#3115)
Co-authored-by: Vladimir Sheremet <[email protected]>
1 parent a155787 commit 5cee4fb

File tree

8 files changed

+90
-2
lines changed

8 files changed

+90
-2
lines changed

packages/vite-node/src/server.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ export class ViteNodeServer {
192192
}
193193

194194
protected async processTransformResult(result: TransformResult) {
195-
return withInlineSourcemap(result)
195+
return withInlineSourcemap(result, {
196+
root: this.server.config.root,
197+
})
196198
}
197199

198200
private async _transformRequest(id: string, customTransformMode?: 'web' | 'ssr') {

packages/vite-node/src/source-map.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { TransformResult } from 'vite'
22
import type { EncodedSourceMap } from '@jridgewell/trace-mapping'
33
import { install } from './source-map-handler'
4+
import { toFilePath } from './utils'
45

56
interface InstallSourceMapSupportOptions {
67
getSourceMap: (source: string) => EncodedSourceMap | null | undefined
@@ -13,13 +14,24 @@ const VITE_NODE_SOURCEMAPPING_SOURCE = '//# sourceMappingSource=vite-node'
1314
const VITE_NODE_SOURCEMAPPING_URL = `${SOURCEMAPPING_URL}=data:application/json;charset=utf-8`
1415
const VITE_NODE_SOURCEMAPPING_REGEXP = new RegExp(`//# ${VITE_NODE_SOURCEMAPPING_URL};base64,(.+)`)
1516

16-
export function withInlineSourcemap(result: TransformResult) {
17+
export function withInlineSourcemap(result: TransformResult, options: {
18+
root: string // project root path of this resource
19+
}) {
1720
const map = result.map
1821
let code = result.code
1922

2023
if (!map || code.includes(VITE_NODE_SOURCEMAPPING_SOURCE))
2124
return result
2225

26+
// sources path from `ViteDevServer` may be not a valid filesystem path (eg. /src/main.js),
27+
// so we try to convert them to valid filesystem path
28+
map.sources = map.sources.map((source) => {
29+
if (!source)
30+
return source
31+
const { exists, path } = toFilePath(source, options.root)
32+
return exists ? path : source
33+
})
34+
2335
// to reduce the payload size, we only inline vite node source map, because it's also the only one we use
2436
const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,(.+)`, 'g')
2537
while (OTHER_SOURCE_MAP_REGEXP.test(code))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { test } from 'vitest'
2+
import { add } from './foo'
3+
4+
test('error in deps', () => {
5+
add()
6+
})

test/stacktraces/fixtures/foo.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line no-undef
2+
export const add = () => bar()

test/stacktraces/test/__snapshots__/runner.test.ts.snap

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`stacktrace should print error frame source file correctly > error-in-deps > error-in-deps 1`] = `
4+
"⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
5+
6+
FAIL error-in-deps.test.js > error in deps
7+
ReferenceError: bar is not defined
8+
❯ Module.add foo.js:2:26
9+
1| // eslint-disable-next-line no-undef
10+
2| export const add = () => bar()
11+
| ^
12+
3|
13+
❯ error-in-deps.test.js:5:3
14+
15+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
16+
"
17+
`;
18+
319
exports[`stacktraces should pick error frame if present > frame.spec.imba > frame.spec.imba 1`] = `
420
" FAIL frame.spec.imba [ frame.spec.imba ]
521
imba-parser error: Unexpected 'CALL_END'
@@ -44,6 +60,13 @@ exports[`stacktraces should respect sourcemaps > add-in-js.test.js > add-in-js.t
4460
"
4561
`;
4662
63+
exports[`stacktraces should respect sourcemaps > error-in-deps.test.js > error-in-deps.test.js 1`] = `
64+
" ❯ error-in-deps.test.js:5:3
65+
66+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
67+
"
68+
`;
69+
4770
exports[`stacktraces should respect sourcemaps > mocked-global.test.js > mocked-global.test.js 1`] = `
4871
" ❯ mocked-global.test.js:6:13
4972
4|

test/stacktraces/test/runner.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,27 @@ describe('stacktraces should pick error frame if present', async () => {
6262
}, 30000)
6363
}
6464
})
65+
66+
describe('stacktrace should print error frame source file correctly', async () => {
67+
const root = resolve(__dirname, '../fixtures')
68+
const testFile = resolve(root, './error-in-deps.test.js')
69+
it('error-in-deps', async () => {
70+
// in Windows child_process is very unstable, we skip testing it
71+
if (process.platform === 'win32' && process.env.CI)
72+
return
73+
74+
const { stderr } = await execa('npx', ['vitest', 'run', testFile], {
75+
cwd: root,
76+
reject: false,
77+
stdio: 'pipe',
78+
env: {
79+
...process.env,
80+
CI: 'true',
81+
NO_COLOR: 'true',
82+
},
83+
})
84+
85+
// expect to print framestack of foo.js
86+
expect(stderr).toMatchSnapshot('error-in-deps')
87+
}, 30000)
88+
})

test/vite-node/src/foo.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const add = (a, b) => a + b

test/vite-node/test/server.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { resolve } from 'pathe'
12
import { ViteNodeServer } from 'vite-node/server'
23
import { describe, expect, test, vi } from 'vitest'
4+
import { createServer } from 'vite'
5+
import { extractSourceMap } from '../../../packages/vite-node/src/source-map'
36

47
describe('server works correctly', async () => {
58
test('resolve id considers transform mode', async () => {
@@ -26,4 +29,19 @@ describe('server works correctly', async () => {
2629
await vnServer.resolveId('/ssr', '/ssr path')
2730
expect(resolveId).toHaveBeenCalledWith('/ssr', '/ssr path', { ssr: true })
2831
})
32+
test('fetchModule with id, and got sourcemap source in absolute path', async () => {
33+
const server = await createServer({
34+
logLevel: 'error',
35+
root: resolve(__dirname, '../'),
36+
})
37+
const vnServer = new ViteNodeServer(server)
38+
39+
// fetchModule in not a valid filesystem path
40+
const fetchResult = await vnServer.fetchModule('/src/foo.js')
41+
42+
const sourceMap = extractSourceMap(fetchResult.code!)
43+
44+
// expect got sourcemap source in a valid filesystem path
45+
expect(sourceMap?.sources[0]).toBe(resolve(__dirname, '../src/foo.js'))
46+
})
2947
})

0 commit comments

Comments
 (0)