@@ -4,6 +4,7 @@ import { describe, expect } from 'vitest'
44import type { ViteDevServer } from '../../..'
55import type { ModuleRunnerContext } from '../../../../module-runner'
66import { ESModulesEvaluator } from '../../../../module-runner'
7+ import { SOURCEMAPPING_URL } from '../../../../shared/constants'
78import {
89 createFixtureEditor ,
910 createModuleRunnerTester ,
@@ -156,6 +157,29 @@ describe('module runner initialization', async () => {
156157 ]
157158 ` )
158159 } )
160+
161+ it ( 'should not crash when preparing a stack trace while globalThis.Buffer is absent' , async ( {
162+ runner,
163+ server,
164+ } ) => {
165+ const mod = await runner . import ( '/fixtures/throws-error-method.ts' )
166+ const methodError = await getError ( ( ) => mod . throwError ( ) )
167+
168+ // Realms may legitimately remove `Buffer` after modules were loaded
169+ // (e.g. browser-parity tests). Preparing a stack in that window must not
170+ // depend on the global still existing.
171+ const bufferBackup = globalThis . Buffer
172+ Reflect . deleteProperty ( globalThis , 'Buffer' )
173+ try {
174+ // `.stack` access lazily invokes `prepareStackTrace`, which decodes the
175+ // inline source map of the runner module
176+ expect ( serializeStack ( server , methodError ) ) . toBe (
177+ ' at Module.throwError (<root>/fixtures/throws-error-method.ts:6:9)' ,
178+ )
179+ } finally {
180+ globalThis . Buffer = bufferBackup
181+ }
182+ } )
159183} )
160184
161185describe ( 'module runner with node:vm executor' , async ( ) => {
@@ -193,3 +217,68 @@ describe('module runner with node:vm executor', async () => {
193217 ) . not . toThrow ( )
194218 } )
195219} )
220+
221+ describe ( 'module runner interceptor with inline data: source map' , async ( ) => {
222+ const syntheticFile = '/virtual-bufferless/inline-map.js'
223+ // Minimal map pointing every generated position at line 1, column 0 of
224+ // "inline-map-original.ts"
225+ const syntheticSourceMap = {
226+ version : 3 ,
227+ sources : [ 'inline-map-original.ts' ] ,
228+ names : [ ] ,
229+ mappings : 'AAAA' ,
230+ }
231+ // The comment token is composed from SOURCEMAPPING_URL so that this spec
232+ // file itself never contains it (see shared/constants.ts)
233+ const syntheticFileContent =
234+ `throw new Error('example')\n` +
235+ `//# ${ SOURCEMAPPING_URL } =data:application/json;base64,${ btoa (
236+ JSON . stringify ( syntheticSourceMap ) ,
237+ ) } \n`
238+
239+ class Evaluator extends ESModulesEvaluator {
240+ async runInlinedModule ( _ : ModuleRunnerContext , __ : string ) {
241+ const initModule = runInThisContext (
242+ '() => { throw new Error("example") }' ,
243+ { filename : syntheticFile } ,
244+ )
245+
246+ initModule ( )
247+ }
248+ }
249+
250+ const it = await createModuleRunnerTester (
251+ { } ,
252+ {
253+ sourcemapInterceptor : {
254+ // Serves a file that is not part of the runner module graph and
255+ // carries an inline base64 source map, so mapping goes through
256+ // `retrieveSourceMap` instead of the runner graph
257+ retrieveFile ( id ) {
258+ if ( id === syntheticFile ) {
259+ return syntheticFileContent
260+ }
261+ } ,
262+ } ,
263+ evaluator : new Evaluator ( ) ,
264+ } ,
265+ )
266+
267+ it ( 'should not crash when decoding an inline source map while globalThis.Buffer is absent' , async ( {
268+ runner,
269+ } ) => {
270+ const error : Error = await runner
271+ . import ( '/fixtures/a.ts' )
272+ . catch ( ( err ) => err )
273+
274+ const bufferBackup = globalThis . Buffer
275+ Reflect . deleteProperty ( globalThis , 'Buffer' )
276+ try {
277+ expect ( error . stack ) . toContain (
278+ '/virtual-bufferless/inline-map-original.ts:1:1' ,
279+ )
280+ } finally {
281+ globalThis . Buffer = bufferBackup
282+ }
283+ } )
284+ } )
0 commit comments