Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
addPluginTemplate,
addServerPlugin,
addTemplate,
addVitePlugin,
createResolver,
defineNuxtModule,
} from '@nuxt/kit';
Expand Down Expand Up @@ -88,7 +89,7 @@ export default defineNuxtModule<ModuleOptions>({
}

if (clientConfigFile || serverConfigFile) {
setupSourceMaps(moduleOptions, nuxt);
setupSourceMaps(moduleOptions, nuxt, addVitePlugin);
}

addOTelCommonJSImportAlias(nuxt);
Expand Down
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/sentryVitePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Nuxt } from '@nuxt/schema';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import type { ConfigEnv, Plugin, UserConfig } from 'vite';
import type { SentryNuxtModuleOptions } from '../common/types';
import { extractNuxtSourceMapSetting, getPluginOptions, validateDifferentSourceMapSettings } from './sourceMaps';

/**
* Creates a Vite plugin that adds the Sentry Vite plugin and validates source map settings.
*/
export function createSentryViteConfigPlugin(options: {
nuxt: Nuxt;
moduleOptions: SentryNuxtModuleOptions;
sourceMapsEnabled: boolean;
shouldDeleteFilesFallback: { client: boolean; server: boolean };
}): Plugin {
const { nuxt, moduleOptions, sourceMapsEnabled, shouldDeleteFilesFallback } = options;
const isDebug = moduleOptions.debug;

return {
name: 'sentry-nuxt-vite-config',
config(viteConfig: UserConfig, env: ConfigEnv) {
// Only run in production builds
if (!sourceMapsEnabled || env.mode === 'development' || nuxt.options?._prepare) {
return;
}

// Detect runtime from Vite config
// In Nuxt, SSR builds have build.ssr: true, client builds don't
const runtime = viteConfig.build?.ssr ? 'server' : 'client';

const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime);

// Initialize build config if needed
viteConfig.build = viteConfig.build || {};
const viteSourceMap = viteConfig.build.sourcemap;

// Vite source map options are the same as the Nuxt source map config options (unless overwritten)
validateDifferentSourceMapSettings({
nuxtSettingKey: `sourcemap.${runtime}`,
nuxtSettingValue: nuxtSourceMapSetting,
otherSettingKey: 'viteConfig.build.sourcemap',
otherSettingValue: viteSourceMap,
});

if (isDebug) {
// eslint-disable-next-line no-console
console.log(`[Sentry] Adding Sentry Vite plugin to the ${runtime} runtime.`);
}

// Add Sentry plugin by mutating the config
// Vite plugin is added on the client and server side (plugin runs for both builds)
// 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.
viteConfig.plugins = viteConfig.plugins || [];
viteConfig.plugins.push(sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)));
},
};
}
61 changes: 25 additions & 36 deletions packages/nuxt/src/vite/sourceMaps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Nuxt } from '@nuxt/schema';
import { sentryRollupPlugin, type SentryRollupPluginOptions } from '@sentry/rollup-plugin';
import { sentryVitePlugin, type SentryVitePluginOptions } from '@sentry/vite-plugin';
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
import type { NitroConfig } from 'nitropack';
import type { Plugin } from 'vite';
import type { SentryNuxtModuleOptions } from '../common/types';
import { createSentryViteConfigPlugin } from './sentryVitePlugin';

/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
Expand All @@ -15,7 +17,11 @@ export type SourceMapSetting = boolean | 'hidden' | 'inline';
/**
* Setup source maps for Sentry inside the Nuxt module during build time (in Vite for Nuxt and Rollup for Nitro).
*/
export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nuxt): void {
export function setupSourceMaps(
moduleOptions: SentryNuxtModuleOptions,
nuxt: Nuxt,
addVitePlugin: (plugin: Plugin | (() => Plugin), options?: { dev?: boolean; build?: boolean }) => void,
): void {
// TODO(v11): remove deprecated options (also from SentryNuxtModuleOptions type)

const isDebug = moduleOptions.debug;
Expand Down Expand Up @@ -76,39 +82,16 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu
}
});

nuxt.hook('vite:extendConfig', async (viteConfig, env) => {
if (sourceMapsEnabled && viteConfig.mode !== 'development' && !nuxt.options?._prepare) {
const runtime = env.isServer ? 'server' : env.isClient ? 'client' : undefined;
const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime);

viteConfig.build = viteConfig.build || {};
const viteSourceMap = viteConfig.build.sourcemap;

// Vite source map options are the same as the Nuxt source map config options (unless overwritten)
validateDifferentSourceMapSettings({
nuxtSettingKey: `sourcemap.${runtime}`,
nuxtSettingValue: nuxtSourceMapSetting,
otherSettingKey: 'viteConfig.build.sourcemap',
otherSettingValue: viteSourceMap,
});

if (isDebug) {
if (!runtime) {
// eslint-disable-next-line no-console
console.log("[Sentry] Cannot detect runtime (client/server) inside hook 'vite:extendConfig'.");
} else {
// eslint-disable-next-line no-console
console.log(`[Sentry] Adding Sentry Vite plugin to the ${runtime} runtime.`);
}
}

// Add Sentry plugin
// Vite plugin is added on the client and server side (hook runs twice)
// 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.
viteConfig.plugins = viteConfig.plugins || [];
viteConfig.plugins.push(sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)));
}
});
addVitePlugin(
createSentryViteConfigPlugin({
nuxt,
moduleOptions,
sourceMapsEnabled,
shouldDeleteFilesFallback,
}),
// Only add source map plugin during build
{ dev: false, build: true },
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale shouldDeleteFilesFallback reference in Vite plugin

High Severity

shouldDeleteFilesFallback is initialized as { client: true, server: true } with let, then reassigned to a new object inside the modules:done hook. However, createSentryViteConfigPlugin is called synchronously before modules:done fires, so it captures a reference to the initial object. When modules:done later reassigns the variable to a new object, the plugin still holds the stale { client: true, server: true } reference. This causes the Vite plugin to always configure fallback source map file deletion, even when users explicitly enabled source maps (where it should be { client: false, server: false }). The old vite:extendConfig hook used a closure reading the variable lazily, so it correctly saw the updated value. The nitro:config hook still works correctly for the same reason.

Additional Locations (2)

Fix in Cursor Fix in Web


nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {
if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) {
Expand Down Expand Up @@ -379,7 +362,13 @@ export function validateNitroSourceMapSettings(
}
}

function validateDifferentSourceMapSettings({
/**
* Validates that source map settings are consistent between Nuxt and Vite/Nitro configurations.
* Logs a warning if conflicting settings are detected.
*
* @internal Only exported for testing.
*/
export function validateDifferentSourceMapSettings({
nuxtSettingKey,
nuxtSettingValue,
otherSettingKey,
Expand Down
Loading
Loading