Skip to content

Commit 6501d52

Browse files
authored
feat(nuxt): Base decision on source maps upload only on Nuxt source map settings (#15859)
There is a total of 6 settings in Nuxt where you can disable/enable your source maps. This is not only difficult to understand when writing and maintaining the logic for this. It may also be hard to debug stuff for users. ![image](https://github.com/user-attachments/assets/73ff315e-2000-4408-ba62-39bd10083378) There is this discussion around Nuxt source maps: #15028 And this issue: #15160 A problem with the current setup was not only the difficulty to understand all of this but also it sometimes just overwrote stuff you didn't want it to. Like in the default case of Nuxt, `sourcemap.server` is `true`, but as `nitro.rollupConfig.output.sourcemap` is always undefined, Sentry would overwrite this setting to `'hidden'`, even though the source maps were enabled already. --- The only two relevant options in Nuxt are the [root-level sourcemap options](https://nuxt.com/docs/guide/going-further/debugging#sourcemaps). Those settings will propagate to nitro, vite and rollup. As we overwrite the source maps setting if the setting is undefined, only the basic Nuxt source map settings (linked above) are taken into account for that from now on.
1 parent 9807efb commit 6501d52

File tree

2 files changed

+338
-199
lines changed

2 files changed

+338
-199
lines changed

Diff for: packages/nuxt/src/vite/sourceMaps.ts

+176-81
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,91 @@ import { consoleSandbox } from '@sentry/core';
33
import { type SentryRollupPluginOptions, sentryRollupPlugin } from '@sentry/rollup-plugin';
44
import { type SentryVitePluginOptions, sentryVitePlugin } from '@sentry/vite-plugin';
55
import type { NitroConfig } from 'nitropack';
6-
import type { OutputOptions } from 'rollup';
76
import type { SentryNuxtModuleOptions } from '../common/types';
87

98
/**
109
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
1110
*/
1211
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
1312

13+
/** A valid source map setting */
14+
export type SourceMapSetting = boolean | 'hidden' | 'inline';
15+
1416
/**
1517
* Setup source maps for Sentry inside the Nuxt module during build time (in Vite for Nuxt and Rollup for Nitro).
1618
*/
1719
export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nuxt): void {
20+
const isDebug = moduleOptions.debug;
21+
1822
const sourceMapsUploadOptions = moduleOptions.sourceMapsUploadOptions || {};
1923
const sourceMapsEnabled = sourceMapsUploadOptions.enabled ?? true;
2024

25+
// In case we overwrite the source map settings, we default to deleting the files
26+
let shouldDeleteFilesFallback = { client: true, server: true };
27+
2128
nuxt.hook('modules:done', () => {
2229
if (sourceMapsEnabled && !nuxt.options.dev) {
23-
changeNuxtSourceMapSettings(nuxt, moduleOptions);
30+
// Changing this setting will propagate:
31+
// - for client to viteConfig.build.sourceMap
32+
// - for server to viteConfig.build.sourceMap and nitro.sourceMap
33+
// On server, nitro.rollupConfig.output.sourcemap remains unaffected from this change.
34+
35+
// ONLY THIS nuxt.sourcemap.(server/client) setting is the one Sentry will eventually overwrite with 'hidden'
36+
const previousSourceMapSettings = changeNuxtSourceMapSettings(nuxt, moduleOptions);
37+
38+
shouldDeleteFilesFallback = {
39+
client: previousSourceMapSettings.client === 'unset',
40+
server: previousSourceMapSettings.server === 'unset',
41+
};
42+
43+
if (
44+
isDebug &&
45+
!sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload &&
46+
(shouldDeleteFilesFallback.client || shouldDeleteFilesFallback.server)
47+
) {
48+
consoleSandbox(() =>
49+
// eslint-disable-next-line no-console
50+
console.log(
51+
"[Sentry] As Sentry enabled `'hidden'` source maps, source maps will be automatically deleted after uploading them to Sentry.",
52+
),
53+
);
54+
}
2455
}
2556
});
2657

27-
nuxt.hook('vite:extendConfig', async (viteConfig, _env) => {
58+
nuxt.hook('vite:extendConfig', async (viteConfig, env) => {
2859
if (sourceMapsEnabled && viteConfig.mode !== 'development') {
29-
const previousUserSourceMapSetting = changeViteSourceMapSettings(viteConfig, moduleOptions);
60+
const runtime = env.isServer ? 'server' : env.isClient ? 'client' : undefined;
61+
const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime);
62+
63+
viteConfig.build = viteConfig.build || {};
64+
const viteSourceMap = viteConfig.build.sourcemap;
65+
66+
// Vite source map options are the same as the Nuxt source map config options (unless overwritten)
67+
validateDifferentSourceMapSettings({
68+
nuxtSettingKey: `sourcemap.${runtime}`,
69+
nuxtSettingValue: nuxtSourceMapSetting,
70+
otherSettingKey: 'viteConfig.build.sourcemap',
71+
otherSettingValue: viteSourceMap,
72+
});
73+
74+
if (isDebug) {
75+
consoleSandbox(() => {
76+
if (!runtime) {
77+
// eslint-disable-next-line no-console
78+
console.log("[Sentry] Cannot detect runtime (client/server) inside hook 'vite:extendConfig'.");
79+
} else {
80+
// eslint-disable-next-line no-console
81+
console.log(`[Sentry] Adding Sentry Vite plugin to the ${runtime} runtime.`);
82+
}
83+
});
84+
}
3085

31-
// Add Sentry plugin
86+
// Add Sentry plugin
87+
// Vite plugin is added on the client and server side (hook runs twice)
88+
// Nuxt client source map is 'false' by default. Warning about this will be shown already in an earlier step, and it's also documented that `nuxt.sourcemap.client` needs to be enabled.
3289
viteConfig.plugins = viteConfig.plugins || [];
33-
viteConfig.plugins.push(
34-
sentryVitePlugin(getPluginOptions(moduleOptions, previousUserSourceMapSetting === 'unset')),
35-
);
90+
viteConfig.plugins.push(sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)));
3691
}
3792
});
3893

@@ -49,11 +104,19 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu
49104
nitroConfig.rollupConfig.plugins = [nitroConfig.rollupConfig.plugins];
50105
}
51106

52-
const previousUserSourceMapSetting = changeRollupSourceMapSettings(nitroConfig, moduleOptions);
107+
validateNitroSourceMapSettings(nuxt, nitroConfig, moduleOptions);
108+
109+
if (isDebug) {
110+
consoleSandbox(() => {
111+
// eslint-disable-next-line no-console
112+
console.log('[Sentry] Adding Sentry Rollup plugin to the server runtime.');
113+
});
114+
}
53115

54116
// Add Sentry plugin
117+
// Runs only on server-side (Nitro)
55118
nitroConfig.rollupConfig.plugins.push(
56-
sentryRollupPlugin(getPluginOptions(moduleOptions, previousUserSourceMapSetting === 'unset')),
119+
sentryRollupPlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)),
57120
);
58121
}
59122
});
@@ -73,15 +136,29 @@ function normalizePath(path: string): string {
73136
*/
74137
export function getPluginOptions(
75138
moduleOptions: SentryNuxtModuleOptions,
76-
deleteFilesAfterUpload: boolean,
139+
shouldDeleteFilesFallback?: { client: boolean; server: boolean },
77140
): SentryVitePluginOptions | SentryRollupPluginOptions {
78141
const sourceMapsUploadOptions = moduleOptions.sourceMapsUploadOptions || {};
79142

80-
if (typeof sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload === 'undefined' && deleteFilesAfterUpload) {
143+
const shouldDeleteFilesAfterUpload = shouldDeleteFilesFallback?.client || shouldDeleteFilesFallback?.server;
144+
const fallbackFilesToDelete = [
145+
...(shouldDeleteFilesFallback?.client ? ['.*/**/public/**/*.map'] : []),
146+
...(shouldDeleteFilesFallback?.server
147+
? ['.*/**/server/**/*.map', '.*/**/output/**/*.map', '.*/**/function/**/*.map']
148+
: []),
149+
];
150+
151+
if (
152+
typeof sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload === 'undefined' &&
153+
shouldDeleteFilesAfterUpload
154+
) {
81155
consoleSandbox(() => {
82156
// eslint-disable-next-line no-console
83157
console.log(
84-
'[Sentry] Setting `sentry.sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [".*/**/public/**/*.map"]` to delete generated source maps after they were uploaded to Sentry.',
158+
`[Sentry] Setting \`sentry.sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${fallbackFilesToDelete
159+
// Logging it as strings in the array
160+
.map(path => `"${path}"`)
161+
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
85162
);
86163
});
87164
}
@@ -114,16 +191,16 @@ export function getPluginOptions(
114191
ignore: sourceMapsUploadOptions.sourcemaps?.ignore ?? undefined,
115192
filesToDeleteAfterUpload: sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload
116193
? sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload
117-
: deleteFilesAfterUpload
118-
? ['.*/**/public/**/*.map']
194+
: shouldDeleteFilesFallback?.server || shouldDeleteFilesFallback?.client
195+
? fallbackFilesToDelete
119196
: undefined,
120197
rewriteSources: (source: string) => normalizePath(source),
121198
...moduleOptions?.unstable_sentryBundlerPluginOptions?.sourcemaps,
122199
},
123200
};
124201
}
125202

126-
/* There are 3 ways to set up source maps (https://github.com/getsentry/sentry-javascript/issues/13993)
203+
/* There are multiple ways to set up source maps (https://github.com/getsentry/sentry-javascript/issues/13993 and https://github.com/getsentry/sentry-javascript/pull/15859)
127204
1. User explicitly disabled source maps
128205
- keep this setting (emit a warning that errors won't be unminified in Sentry)
129206
- We will not upload anything
@@ -133,11 +210,24 @@ export function getPluginOptions(
133210
- we enable 'hidden' source maps generation
134211
- configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this)
135212
136-
Nuxt has 3 places to set source maps: vite options, rollup options, nuxt itself
137-
Ideally, all 3 are enabled to get all source maps.
213+
Users only have to explicitly enable client source maps. Sentry only overwrites the base Nuxt source map settings as they propagate.
138214
*/
139215

140-
/** only exported for testing */
216+
/** only exported for tests */
217+
export function extractNuxtSourceMapSetting(
218+
nuxt: { options: { sourcemap?: SourceMapSetting | { server?: SourceMapSetting; client?: SourceMapSetting } } },
219+
runtime: 'client' | 'server' | undefined,
220+
): SourceMapSetting | undefined {
221+
if (!runtime) {
222+
return undefined;
223+
} else {
224+
return typeof nuxt.options?.sourcemap === 'boolean' || typeof nuxt.options?.sourcemap === 'string'
225+
? nuxt.options.sourcemap
226+
: nuxt.options?.sourcemap?.[runtime];
227+
}
228+
}
229+
230+
/** only exported for testing */
141231
export function changeNuxtSourceMapSettings(
142232
nuxt: Nuxt,
143233
sentryModuleOptions: SentryNuxtModuleOptions,
@@ -160,7 +250,7 @@ export function changeNuxtSourceMapSettings(
160250

161251
case 'hidden':
162252
case true:
163-
logKeepSourceMapSetting(sentryModuleOptions, 'sourcemap', (nuxtSourceMap as true).toString());
253+
logKeepEnabledSourceMapSetting(sentryModuleOptions, 'sourcemap', (nuxtSourceMap as true).toString());
164254
previousUserSourceMapSetting = { client: 'enabled', server: 'enabled' };
165255
break;
166256
case undefined:
@@ -175,7 +265,7 @@ export function changeNuxtSourceMapSettings(
175265
warnExplicitlyDisabledSourceMap('sourcemap.client');
176266
previousUserSourceMapSetting.client = 'disabled';
177267
} else if (['hidden', true].includes(nuxtSourceMap.client)) {
178-
logKeepSourceMapSetting(sentryModuleOptions, 'sourcemap.client', nuxtSourceMap.client.toString());
268+
logKeepEnabledSourceMapSetting(sentryModuleOptions, 'sourcemap.client', nuxtSourceMap.client.toString());
179269
previousUserSourceMapSetting.client = 'enabled';
180270
} else {
181271
nuxt.options.sourcemap.client = 'hidden';
@@ -187,7 +277,7 @@ export function changeNuxtSourceMapSettings(
187277
warnExplicitlyDisabledSourceMap('sourcemap.server');
188278
previousUserSourceMapSetting.server = 'disabled';
189279
} else if (['hidden', true].includes(nuxtSourceMap.server)) {
190-
logKeepSourceMapSetting(sentryModuleOptions, 'sourcemap.server', nuxtSourceMap.server.toString());
280+
logKeepEnabledSourceMapSetting(sentryModuleOptions, 'sourcemap.server', nuxtSourceMap.server.toString());
191281
previousUserSourceMapSetting.server = 'enabled';
192282
} else {
193283
nuxt.options.sourcemap.server = 'hidden';
@@ -199,74 +289,79 @@ export function changeNuxtSourceMapSettings(
199289
return previousUserSourceMapSetting;
200290
}
201291

202-
/** only exported for testing */
203-
export function changeViteSourceMapSettings(
204-
viteConfig: { build?: { sourcemap?: boolean | 'inline' | 'hidden' } },
292+
/** Logs warnings about potentially conflicting source map settings.
293+
* Configures `sourcemapExcludeSources` in Nitro to make source maps usable in Sentry.
294+
*
295+
* only exported for testing
296+
*/
297+
export function validateNitroSourceMapSettings(
298+
nuxt: { options: { sourcemap?: SourceMapSetting | { server?: SourceMapSetting } } },
299+
nitroConfig: NitroConfig,
205300
sentryModuleOptions: SentryNuxtModuleOptions,
206-
): UserSourceMapSetting {
207-
viteConfig.build = viteConfig.build || {};
208-
const viteSourceMap = viteConfig.build.sourcemap;
209-
210-
let previousUserSourceMapSetting: UserSourceMapSetting;
211-
212-
if (viteSourceMap === false) {
213-
warnExplicitlyDisabledSourceMap('vite.build.sourcemap');
214-
previousUserSourceMapSetting = 'disabled';
215-
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
216-
logKeepSourceMapSetting(sentryModuleOptions, 'vite.build.sourcemap', viteSourceMap.toString());
217-
previousUserSourceMapSetting = 'enabled';
218-
} else {
219-
viteConfig.build.sourcemap = 'hidden';
220-
logSentryEnablesSourceMap('vite.build.sourcemap', 'hidden');
221-
previousUserSourceMapSetting = 'unset';
222-
}
301+
): void {
302+
const isDebug = sentryModuleOptions.debug;
303+
const nuxtSourceMap = extractNuxtSourceMapSetting(nuxt, 'server');
223304

224-
return previousUserSourceMapSetting;
225-
}
305+
// NITRO CONFIG ---
306+
307+
validateDifferentSourceMapSettings({
308+
nuxtSettingKey: 'sourcemap.server',
309+
nuxtSettingValue: nuxtSourceMap,
310+
otherSettingKey: 'nitro.sourceMap',
311+
otherSettingValue: nitroConfig.sourceMap,
312+
});
313+
314+
// ROLLUP CONFIG ---
226315

227-
/** only exported for testing */
228-
export function changeRollupSourceMapSettings(
229-
nitroConfig: {
230-
rollupConfig?: {
231-
output?: {
232-
sourcemap?: OutputOptions['sourcemap'];
233-
sourcemapExcludeSources?: OutputOptions['sourcemapExcludeSources'];
234-
};
235-
};
236-
},
237-
sentryModuleOptions: SentryNuxtModuleOptions,
238-
): UserSourceMapSetting {
239316
nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
240317
nitroConfig.rollupConfig.output = nitroConfig.rollupConfig.output || { sourcemap: undefined };
318+
const nitroRollupSourceMap = nitroConfig.rollupConfig.output.sourcemap;
241319

242-
let previousUserSourceMapSetting: UserSourceMapSetting;
243-
244-
const nitroSourceMap = nitroConfig.rollupConfig.output.sourcemap;
320+
// We don't override nitro.rollupConfig.output.sourcemap (undefined by default, but overrides all other server-side source map settings)
321+
if (typeof nitroRollupSourceMap !== 'undefined' && ['hidden', 'inline', true, false].includes(nitroRollupSourceMap)) {
322+
const settingKey = 'nitro.rollupConfig.output.sourcemap';
245323

246-
if (nitroSourceMap === false) {
247-
warnExplicitlyDisabledSourceMap('nitro.rollupConfig.output.sourcemap');
248-
previousUserSourceMapSetting = 'disabled';
249-
} else if (nitroSourceMap && ['hidden', 'inline', true].includes(nitroSourceMap)) {
250-
logKeepSourceMapSetting(sentryModuleOptions, 'nitro.rollupConfig.output.sourcemap', nitroSourceMap.toString());
251-
previousUserSourceMapSetting = 'enabled';
252-
} else {
253-
nitroConfig.rollupConfig.output.sourcemap = 'hidden';
254-
logSentryEnablesSourceMap('nitro.rollupConfig.output.sourcemap', 'hidden');
255-
previousUserSourceMapSetting = 'unset';
324+
validateDifferentSourceMapSettings({
325+
nuxtSettingKey: 'sourcemap.server',
326+
nuxtSettingValue: nuxtSourceMap,
327+
otherSettingKey: settingKey,
328+
otherSettingValue: nitroRollupSourceMap,
329+
});
256330
}
257331

258332
nitroConfig.rollupConfig.output.sourcemapExcludeSources = false;
259-
consoleSandbox(() => {
260-
// eslint-disable-next-line no-console
261-
console.log(
262-
'[Sentry] Disabled source map setting in the Nuxt config: `nitro.rollupConfig.output.sourcemapExcludeSources`. Source maps will include the actual code to be able to un-minify code snippets in Sentry.',
263-
);
264-
});
333+
if (isDebug) {
334+
consoleSandbox(() => {
335+
// eslint-disable-next-line no-console
336+
console.log(
337+
'[Sentry] Set `sourcemapExcludeSources: false` in the Nuxt config (`nitro.rollupConfig.output`). Source maps will now include the actual code to be able to un-minify code snippets in Sentry.',
338+
);
339+
});
340+
}
341+
}
265342

266-
return previousUserSourceMapSetting;
343+
function validateDifferentSourceMapSettings({
344+
nuxtSettingKey,
345+
nuxtSettingValue,
346+
otherSettingKey,
347+
otherSettingValue,
348+
}: {
349+
nuxtSettingKey: string;
350+
nuxtSettingValue?: SourceMapSetting;
351+
otherSettingKey: string;
352+
otherSettingValue?: SourceMapSetting;
353+
}): void {
354+
if (nuxtSettingValue !== otherSettingValue) {
355+
consoleSandbox(() => {
356+
// eslint-disable-next-line no-console
357+
console.warn(
358+
`[Sentry] Source map generation settings are conflicting. Sentry uses \`${nuxtSettingKey}: ${nuxtSettingValue}\`. However, a conflicting setting was discovered (\`${otherSettingKey}: ${otherSettingValue}\`). This setting was probably explicitly set in your configuration. Sentry won't override this setting but it may affect source maps generation and upload. Without source maps, code snippets on the Sentry Issues page will remain minified.`,
359+
);
360+
});
361+
}
267362
}
268363

269-
function logKeepSourceMapSetting(
364+
function logKeepEnabledSourceMapSetting(
270365
sentryNuxtModuleOptions: SentryNuxtModuleOptions,
271366
settingKey: string,
272367
settingValue: string,
@@ -275,24 +370,24 @@ function logKeepSourceMapSetting(
275370
consoleSandbox(() => {
276371
// eslint-disable-next-line no-console
277372
console.log(
278-
`[Sentry] We discovered \`${settingKey}\` is set to \`${settingValue}\`. Sentry will keep this source map setting. This will un-minify the code snippet on the Sentry Issue page.`,
373+
`[Sentry] \`${settingKey}\` is enabled with \`${settingValue}\`. This will correctly un-minify the code snippet on the Sentry Issue Details page.`,
279374
);
280375
});
281376
}
282377
}
283378

284379
function warnExplicitlyDisabledSourceMap(settingKey: string): void {
285380
consoleSandbox(() => {
286-
// eslint-disable-next-line no-console
381+
// eslint-disable-next-line no-console
287382
console.warn(
288-
`[Sentry] Parts of source map generation are currently disabled in your Nuxt configuration (\`${settingKey}: false\`). This setting is either a default setting or was explicitly set in your configuration. Sentry won't override this setting. Without source maps, code snippets on the Sentry Issues page will remain minified. To show unminified code, enable source maps in \`${settingKey}\` (e.g. by setting them to \`hidden\`).`,
383+
`[Sentry] We discovered \`${settingKey}\` is set to \`false\`. This setting is either a default setting or was explicitly set in your configuration. Sentry won't override this setting. Without source maps, code snippets on the Sentry Issues page will remain minified. To show unminified code, enable source maps in \`${settingKey}\` (e.g. by setting them to \`'hidden'\`).`,
289384
);
290385
});
291386
}
292387

293388
function logSentryEnablesSourceMap(settingKey: string, settingValue: string): void {
294389
consoleSandbox(() => {
295-
// eslint-disable-next-line no-console
390+
// eslint-disable-next-line no-console
296391
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: ${settingValue}\`.`);
297392
});
298393
}

0 commit comments

Comments
 (0)