Skip to content

Commit c5a201e

Browse files
committed
fix: Ensure inline sourcemaps are generated for wrapped modules
1 parent 2fb0f99 commit c5a201e

2 files changed

Lines changed: 67 additions & 9 deletions

File tree

packages/nextjs/src/config/loaders/wrappingLoader.ts

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
250255
async 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.

packages/nextjs/src/config/webpack.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export function constructWebpackConfigFunction({
150150
projectDir,
151151
rawNewConfig.resolve?.modules,
152152
),
153+
isDev,
153154
};
154155

155156
const normalizeLoaderResourcePath = (resourcePath: string): string => {

0 commit comments

Comments
 (0)