@@ -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 > =>
@@ -267,21 +274,48 @@ async function wrapUserCode(
267274 resolveId : id => {
268275 if ( id === SENTRY_WRAPPER_MODULE_NAME || id === WRAPPING_TARGET_MODULE_NAME ) {
269276 return id ;
270- } else {
271- return null ;
272277 }
278+
279+ return null ;
273280 } ,
274281 load ( id ) {
275282 if ( id === SENTRY_WRAPPER_MODULE_NAME ) {
276283 return withDefaultExport ? wrapperCode : wrapperCode . replace ( 'export { default } from' , 'export {} from' ) ;
277- } else if ( id === WRAPPING_TARGET_MODULE_NAME ) {
284+ }
285+
286+ if ( id !== WRAPPING_TARGET_MODULE_NAME ) {
287+ return null ;
288+ }
289+
290+ // In prod/build, we should not interfere with sourcemaps
291+ if ( ! isDev || ! userModulePath ) {
292+ return { code : userModuleCode , map : userModuleSourceMap } ;
293+ }
294+
295+ // In dev mode, we need to adjust the sourcemap to use absolute paths for the user's file.
296+ // This ensures debugger breakpoints correctly map back to the original file.
297+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
298+ const userSources : string [ ] = userModuleSourceMap ?. sources ;
299+ if ( Array . isArray ( userSources ) ) {
278300 return {
279301 code : userModuleCode ,
280- map : userModuleSourceMap , // give rollup access to original user module source map
302+ map : {
303+ ...userModuleSourceMap ,
304+ sources : userSources . map ( ( source : string , index : number ) => ( index === 0 ? userModulePath : source ) ) ,
305+ } ,
281306 } ;
282- } else {
283- return null ;
284307 }
308+
309+ // If no sourcemap exists, create a simple identity mapping with the absolute path
310+ return {
311+ code : userModuleCode ,
312+ map : {
313+ version : 3 ,
314+ sources : [ userModulePath ] ,
315+ sourcesContent : [ userModuleCode ] ,
316+ mappings : '' ,
317+ } ,
318+ } ;
285319 } ,
286320 } ,
287321
@@ -352,7 +386,22 @@ async function wrapUserCode(
352386
353387 const finalBundle = await rollupBuild . generate ( {
354388 format : 'esm' ,
355- sourcemap : 'hidden' , // put source map data in the bundle but don't generate a source map comment in the output
389+ // In dev mode, use inline sourcemaps so debuggers can map breakpoints back to original source.
390+ // In production, use hidden sourcemaps (no sourceMappingURL comment) to avoid exposing internals.
391+ sourcemap : isDev ? 'inline' : 'hidden' ,
392+ // In dev mode, preserve absolute paths in sourcemaps so debuggers can correctly resolve breakpoints.
393+ // By default, Rollup converts absolute paths to relative paths, which breaks debugging.
394+ // We only do this in dev mode to avoid interfering with Sentry's sourcemap upload in production.
395+ sourcemapPathTransform : isDev
396+ ? relativeSourcePath => {
397+ // If we have userModulePath and this relative path matches the end of it, use the absolute path
398+ if ( userModulePath ?. endsWith ( relativeSourcePath ) ) {
399+ return userModulePath ;
400+ }
401+ // Keep other paths (like sentry-wrapper-module) as-is
402+ return relativeSourcePath ;
403+ }
404+ : undefined ,
356405 } ) ;
357406
358407 // The module at index 0 is always the entrypoint, which in this case is the proxy module.
0 commit comments