Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stack trace point to incorrect file (#3004) #3115

Merged
merged 4 commits into from
Apr 5, 2023
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
4 changes: 3 additions & 1 deletion packages/vite-node/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ export class ViteNodeServer {
}

protected async processTransformResult(result: TransformResult) {
return withInlineSourcemap(result)
return withInlineSourcemap(result, {
root: this.server.config.root,
})
}

private async _transformRequest(id: string, customTransformMode?: 'web' | 'ssr') {
Expand Down
14 changes: 13 additions & 1 deletion packages/vite-node/src/source-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TransformResult } from 'vite'
import type { EncodedSourceMap } from '@jridgewell/trace-mapping'
import { install } from './source-map-handler'
import { toFilePath } from './utils'

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

export function withInlineSourcemap(result: TransformResult) {
export function withInlineSourcemap(result: TransformResult, options: {
root: string // project root path of this resource
}) {
const map = result.map
let code = result.code

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

// sources path from `ViteDevServer` may be not a valid filesystem path (eg. /src/main.js),
// so we try to convert them to valid filesystem path
map.sources = map.sources.map((source) => {
if (!source)
return source
const { exists, path } = toFilePath(source, options.root)
return exists ? path : source
})

// to reduce the payload size, we only inline vite node source map, because it's also the only one we use
const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,(.+)`, 'g')
while (OTHER_SOURCE_MAP_REGEXP.test(code))
Expand Down
6 changes: 6 additions & 0 deletions test/stacktraces/fixtures/error-in-deps.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { test } from 'vitest'
import { add } from './foo'

test('error in deps', () => {
add()
})
2 changes: 2 additions & 0 deletions test/stacktraces/fixtures/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-undef
export const add = () => bar()
23 changes: 23 additions & 0 deletions test/stacktraces/test/__snapshots__/runner.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`stacktrace should print error frame source file correctly > error-in-deps > error-in-deps 1`] = `
"⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL error-in-deps.test.js > error in deps
ReferenceError: bar is not defined
❯ Module.add foo.js:2:26
1| // eslint-disable-next-line no-undef
2| export const add = () => bar()
| ^
3|
❯ error-in-deps.test.js:5:3

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
"
`;

exports[`stacktraces should pick error frame if present > frame.spec.imba > frame.spec.imba 1`] = `
" FAIL frame.spec.imba [ frame.spec.imba ]
imba-parser error: Unexpected 'CALL_END'
Expand Down Expand Up @@ -44,6 +60,13 @@ exports[`stacktraces should respect sourcemaps > add-in-js.test.js > add-in-js.t
"
`;

exports[`stacktraces should respect sourcemaps > error-in-deps.test.js > error-in-deps.test.js 1`] = `
" ❯ error-in-deps.test.js:5:3

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
"
`;

exports[`stacktraces should respect sourcemaps > mocked-global.test.js > mocked-global.test.js 1`] = `
" ❯ mocked-global.test.js:6:13
4|
Expand Down
24 changes: 24 additions & 0 deletions test/stacktraces/test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,27 @@ describe('stacktraces should pick error frame if present', async () => {
}, 30000)
}
})

describe('stacktrace should print error frame source file correctly', async () => {
const root = resolve(__dirname, '../fixtures')
const testFile = resolve(root, './error-in-deps.test.js')
it('error-in-deps', async () => {
// in Windows child_process is very unstable, we skip testing it
if (process.platform === 'win32' && process.env.CI)
return

const { stderr } = await execa('npx', ['vitest', 'run', testFile], {
cwd: root,
reject: false,
stdio: 'pipe',
env: {
...process.env,
CI: 'true',
NO_COLOR: 'true',
},
})

// expect to print framestack of foo.js
expect(stderr).toMatchSnapshot('error-in-deps')
}, 30000)
})
1 change: 1 addition & 0 deletions test/vite-node/src/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const add = (a, b) => a + b
18 changes: 18 additions & 0 deletions test/vite-node/test/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { resolve } from 'pathe'
import { ViteNodeServer } from 'vite-node/server'
import { describe, expect, test, vi } from 'vitest'
import { createServer } from 'vite'
import { extractSourceMap } from '../../../packages/vite-node/src/source-map'

describe('server works correctly', async () => {
test('resolve id considers transform mode', async () => {
Expand All @@ -26,4 +29,19 @@ describe('server works correctly', async () => {
await vnServer.resolveId('/ssr', '/ssr path')
expect(resolveId).toHaveBeenCalledWith('/ssr', '/ssr path', { ssr: true })
})
test('fetchModule with id, and got sourcemap source in absolute path', async () => {
const server = await createServer({
logLevel: 'error',
root: resolve(__dirname, '../'),
})
const vnServer = new ViteNodeServer(server)

// fetchModule in not a valid filesystem path
const fetchResult = await vnServer.fetchModule('/src/foo.js')

const sourceMap = extractSourceMap(fetchResult.code!)

// expect got sourcemap source in a valid filesystem path
expect(sourceMap?.sources[0]).toBe(resolve(__dirname, '../src/foo.js'))
})
})