Skip to content

Commit be95050

Browse files
sapphi-redVap0r1ze
andauthoredApr 4, 2023
fix(ssr): load sourcemaps alongside modules (#11780)
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
1 parent 4538bfe commit be95050

File tree

5 files changed

+105
-4
lines changed

5 files changed

+105
-4
lines changed
 

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

+25-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { transformRequest } from '../server/transformRequest'
1212
import type { InternalResolveOptionsWithOverrideConditions } from '../plugins/resolve'
1313
import { tryNodeResolve } from '../plugins/resolve'
14+
import { genSourceMapUrl } from '../server/sourcemap'
1415
import {
1516
ssrDynamicImportKey,
1617
ssrExportAllKey,
@@ -26,6 +27,16 @@ interface SSRContext {
2627

2728
type SSRModule = Record<string, any>
2829

30+
// eslint-disable-next-line @typescript-eslint/no-empty-function
31+
const AsyncFunction = async function () {}.constructor as typeof Function
32+
let fnDeclarationLineCount = 0
33+
{
34+
const body = '/*code*/'
35+
const source = new AsyncFunction('a', 'b', body).toString()
36+
fnDeclarationLineCount =
37+
source.slice(0, source.indexOf(body)).split('\n').length - 1
38+
}
39+
2940
const pendingModules = new Map<string, Promise<SSRModule>>()
3041
const pendingImports = new Map<string, string[]>()
3142
const importErrors = new WeakMap<Error, { importee: string }>()
@@ -190,17 +201,28 @@ async function instantiateModule(
190201
}
191202
}
192203

204+
let sourceMapSuffix = ''
205+
if (result.map) {
206+
const moduleSourceMap = Object.assign({}, result.map, {
207+
// currently we need to offset the line
208+
// https://github.com/nodejs/node/issues/43047#issuecomment-1180632750
209+
mappings: ';'.repeat(fnDeclarationLineCount) + result.map.mappings,
210+
})
211+
sourceMapSuffix =
212+
'\n//# sourceMappingURL=' + genSourceMapUrl(moduleSourceMap)
213+
}
214+
193215
try {
194-
// eslint-disable-next-line @typescript-eslint/no-empty-function
195-
const AsyncFunction = async function () {}.constructor as typeof Function
196216
const initModule = new AsyncFunction(
197217
`global`,
198218
ssrModuleExportsKey,
199219
ssrImportMetaKey,
200220
ssrImportKey,
201221
ssrDynamicImportKey,
202222
ssrExportAllKey,
203-
'"use strict";' + result.code + `\n//# sourceURL=${mod.url}`,
223+
'"use strict";' +
224+
result.code +
225+
`\n//# sourceURL=${mod.url}${sourceMapSuffix}`,
204226
)
205227
await initModule(
206228
context.global,

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

+27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { execFile } from 'node:child_process'
2+
import { promisify } from 'node:util'
3+
import path from 'node:path'
14
import fetch from 'node-fetch'
25
import { describe, expect, test } from 'vitest'
36
import { port } from './serve'
@@ -55,3 +58,27 @@ describe.runIf(isServe)('hmr', () => {
5558
}, '[wow]')
5659
})
5760
})
61+
62+
describe.runIf(isServe)('stacktrace', () => {
63+
const execFileAsync = promisify(execFile)
64+
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+
)
73+
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+
})
83+
}
84+
})

‎playground/ssr-html/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"scripts": {
77
"dev": "node server",
88
"serve": "NODE_ENV=production node server",
9-
"debug": "node --inspect-brk server"
9+
"debug": "node --inspect-brk server",
10+
"test-stacktrace:off": "node test-stacktrace false",
11+
"test-stacktrace:on": "node test-stacktrace true"
1012
},
1113
"dependencies": {},
1214
"devDependencies": {

‎playground/ssr-html/src/error.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function error() {
2+
throw new Error('e')
3+
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import path from 'node:path'
2+
import { fileURLToPath } from 'node:url'
3+
import { createServer } from 'vite'
4+
5+
const isSourceMapEnabled = process.argv[2] === 'true'
6+
process.setSourceMapsEnabled(isSourceMapEnabled)
7+
console.log('# sourcemaps enabled:', isSourceMapEnabled)
8+
9+
const version = (() => {
10+
const m = process.version.match(/^v(\d+)\.(\d+)\.\d+$/)
11+
if (!m) throw new Error(`Failed to parse version: ${process.version}`)
12+
13+
return { major: +m[1], minor: +m[2] }
14+
})()
15+
16+
// https://github.com/nodejs/node/pull/43428
17+
const isFunctionSourceMapSupported =
18+
(version.major === 16 && version.minor >= 17) ||
19+
(version.major === 18 && version.minor >= 6) ||
20+
version.major >= 19
21+
22+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
23+
const isTest = process.env.VITEST
24+
25+
const vite = await createServer({
26+
root: __dirname,
27+
logLevel: isTest ? 'error' : 'info',
28+
server: {
29+
middlewareMode: true,
30+
},
31+
appType: 'custom',
32+
})
33+
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)
43+
}
44+
console.log(e)
45+
}
46+
47+
await vite.close()

0 commit comments

Comments
 (0)
Please sign in to comment.