@@ -43,6 +43,7 @@ export type WrappingLoaderOptions = {
4343 wrappingTargetKind : 'page' | 'api-route' | 'middleware' | 'server-component' | 'route-handler' ;
4444 vercelCronsConfig ?: VercelCronsConfig ;
4545 nextjsRequestAsyncStorageModulePath ?: string ;
46+ isDev ?: boolean ;
4647} ;
4748
4849/**
@@ -66,6 +67,7 @@ export default function wrappingLoader(
6667 wrappingTargetKind,
6768 vercelCronsConfig,
6869 nextjsRequestAsyncStorageModulePath,
70+ isDev,
6971 } = 'getOptions' in this ? this . getOptions ( ) : this . query ;
7072
7173 this . async ( ) ;
@@ -220,7 +222,7 @@ export default function wrappingLoader(
220222
221223 // Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
222224 // individual exports (which nextjs seems to require).
223- wrapUserCode ( templateCode , userCode , userModuleSourceMap )
225+ wrapUserCode ( templateCode , userCode , userModuleSourceMap , isDev , this . resourcePath )
224226 . then ( ( { code : wrappedCode , map : wrappedCodeSourceMap } ) => {
225227 this . callback ( null , wrappedCode , wrappedCodeSourceMap ) ;
226228 } )
@@ -245,13 +247,18 @@ export default function wrappingLoader(
245247 *
246248 * @param wrapperCode The wrapper module code
247249 * @param userModuleCode The user module code
250+ * @param userModuleSourceMap The source map for the user module
251+ * @param isDev Whether we're in development mode (affects sourcemap generation)
252+ * @param userModulePath The absolute path to the user's original module (for sourcemap accuracy)
248253 * @returns The wrapped user code and a source map that describes the transformations done by this function
249254 */
250255async function wrapUserCode (
251256 wrapperCode : string ,
252257 userModuleCode : string ,
253258 // eslint-disable-next-line @typescript-eslint/no-explicit-any
254259 userModuleSourceMap : any ,
260+ isDev ?: boolean ,
261+ userModulePath ?: string ,
255262 // eslint-disable-next-line @typescript-eslint/no-explicit-any
256263) : Promise < { code : string ; map ?: any } > {
257264 const wrap = ( withDefaultExport : boolean ) : Promise < RollupBuild > =>
@@ -265,23 +272,54 @@ async function wrapUserCode(
265272 {
266273 name : 'virtualize-sentry-wrapper-modules' ,
267274 resolveId : id => {
275+ if ( isDev && id === WRAPPING_TARGET_MODULE_NAME ) {
276+ return userModulePath || id ;
277+ }
278+
268279 if ( id === SENTRY_WRAPPER_MODULE_NAME || id === WRAPPING_TARGET_MODULE_NAME ) {
269280 return id ;
270- } else {
271- return null ;
272281 }
282+
283+ return null ;
273284 } ,
274285 load ( id ) {
275286 if ( id === SENTRY_WRAPPER_MODULE_NAME ) {
276287 return withDefaultExport ? wrapperCode : wrapperCode . replace ( 'export { default } from' , 'export {} from' ) ;
277- } else if ( id === WRAPPING_TARGET_MODULE_NAME ) {
288+ }
289+
290+ if ( id !== WRAPPING_TARGET_MODULE_NAME && ! ( isDev && id === userModulePath ) ) {
291+ return null ;
292+ }
293+
294+ // In prod/build, we should not interfere with sourcemaps
295+ if ( ! isDev || ! userModulePath ) {
296+ return { code : userModuleCode , map : userModuleSourceMap } ;
297+ }
298+
299+ // In dev mode, we need to adjust the sourcemap to use absolute paths for the user's file.
300+ // This ensures debugger breakpoints correctly map back to the original file.
301+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
302+ const userSources : string [ ] = userModuleSourceMap ?. sources ;
303+ if ( Array . isArray ( userSources ) ) {
278304 return {
279305 code : userModuleCode ,
280- map : userModuleSourceMap , // give rollup access to original user module source map
306+ map : {
307+ ...userModuleSourceMap ,
308+ sources : userSources . map ( ( source : string , index : number ) => ( index === 0 ? userModulePath : source ) ) ,
309+ } ,
281310 } ;
282- } else {
283- return null ;
284311 }
312+
313+ // If no sourcemap exists, create a simple identity mapping with the absolute path
314+ return {
315+ code : userModuleCode ,
316+ map : {
317+ version : 3 ,
318+ sources : [ userModulePath ] ,
319+ sourcesContent : [ userModuleCode ] ,
320+ mappings : '' ,
321+ } ,
322+ } ;
285323 } ,
286324 } ,
287325
@@ -301,7 +339,11 @@ async function wrapUserCode(
301339 ] ,
302340
303341 // We only want to bundle our wrapper module and the wrappee module into one, so we mark everything else as external.
304- external : sourceId => sourceId !== SENTRY_WRAPPER_MODULE_NAME && sourceId !== WRAPPING_TARGET_MODULE_NAME ,
342+ // In dev mode, we also need to include userModulePath since resolveId returns the absolute path for debugging.
343+ external : sourceId =>
344+ sourceId !== SENTRY_WRAPPER_MODULE_NAME &&
345+ sourceId !== WRAPPING_TARGET_MODULE_NAME &&
346+ ! ( isDev && sourceId === userModulePath ) ,
305347
306348 // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the
307349 // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and
@@ -352,7 +394,22 @@ async function wrapUserCode(
352394
353395 const finalBundle = await rollupBuild . generate ( {
354396 format : 'esm' ,
355- sourcemap : 'hidden' , // put source map data in the bundle but don't generate a source map comment in the output
397+ // In dev mode, use inline sourcemaps so debuggers can map breakpoints back to original source.
398+ // In production, use hidden sourcemaps (no sourceMappingURL comment) to avoid exposing internals.
399+ sourcemap : isDev ? 'inline' : 'hidden' ,
400+ // In dev mode, preserve absolute paths in sourcemaps so debuggers can correctly resolve breakpoints.
401+ // By default, Rollup converts absolute paths to relative paths, which breaks debugging.
402+ // We only do this in dev mode to avoid interfering with Sentry's sourcemap upload in production.
403+ sourcemapPathTransform : isDev
404+ ? relativeSourcePath => {
405+ // If we have userModulePath and this relative path matches the end of it, use the absolute path
406+ if ( userModulePath ?. endsWith ( relativeSourcePath ) ) {
407+ return userModulePath ;
408+ }
409+ // Keep other paths (like sentry-wrapper-module) as-is
410+ return relativeSourcePath ;
411+ }
412+ : undefined ,
356413 } ) ;
357414
358415 // The module at index 0 is always the entrypoint, which in this case is the proxy module.
0 commit comments