Skip to content

Commit

Permalink
fix: stack trace point to incorrect file (#3004) (#3115)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
  • Loading branch information
ChenKS12138 and sheremet-va authored Apr 5, 2023
1 parent a155787 commit 5cee4fb
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 2 deletions.
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'))
})
})

0 comments on commit 5cee4fb

Please sign in to comment.