-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(nuxt): Upload client source maps #19805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| import type { Nuxt } from '@nuxt/schema'; | ||
| import { sentryRollupPlugin, type SentryRollupPluginOptions } from '@sentry/rollup-plugin'; | ||
| import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; | ||
| import { sentryVitePlugin, 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'; | ||
| import { validateSourceMapsOptionsPlugin } from './sentryVitePlugin'; | ||
|
|
||
| /** | ||
| * Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps | ||
|
|
@@ -20,7 +20,7 @@ export type SourceMapSetting = boolean | 'hidden' | 'inline'; | |
| export function setupSourceMaps( | ||
| moduleOptions: SentryNuxtModuleOptions, | ||
| nuxt: Nuxt, | ||
| addVitePlugin: (plugin: Plugin | (() => Plugin), options?: { dev?: boolean; build?: boolean }) => void, | ||
| addVitePlugin: (plugin: Plugin[], options?: { dev?: boolean; build?: boolean }) => void, | ||
| ): void { | ||
| // TODO(v11): remove deprecated options (also from SentryNuxtModuleOptions type) | ||
|
|
||
|
|
@@ -81,16 +81,16 @@ export function setupSourceMaps( | |
| } | ||
| }); | ||
|
|
||
| addVitePlugin( | ||
| createSentryViteConfigPlugin({ | ||
| nuxt, | ||
| moduleOptions, | ||
| sourceMapsEnabled, | ||
| shouldDeleteFilesFallback, | ||
| }), | ||
| // Only add source map plugin during build | ||
| { dev: false, build: true }, | ||
| ); | ||
| if (sourceMapsEnabled && !nuxt.options.dev && !nuxt.options?._prepare) { | ||
| addVitePlugin( | ||
| [ | ||
| validateSourceMapsOptionsPlugin({ nuxt, moduleOptions, sourceMapsEnabled }), | ||
| // Vite plugin is added on the client and server side (plugin runs for both builds) | ||
| ...sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback)), | ||
| ], | ||
| { dev: false, build: true }, // Only add source map plugin during build | ||
| ); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vite plugin options read before modules:done hook mutates themHigh Severity
Additional Locations (2) |
||
|
|
||
| nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => { | ||
| if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,16 @@ | |
| import type { SourceMapSetting } from '../../src/vite/sourceMaps'; | ||
|
|
||
| function createMockAddVitePlugin() { | ||
| let capturedPlugin: Plugin | null = null; | ||
| let capturedPlugins: Plugin[] | null = null; | ||
|
|
||
| const mockAddVitePlugin = vi.fn((plugin: Plugin | (() => Plugin)) => { | ||
| capturedPlugin = typeof plugin === 'function' ? plugin() : plugin; | ||
| const mockAddVitePlugin = vi.fn((plugins: Plugin[]) => { | ||
| capturedPlugins = plugins; | ||
| }); | ||
|
|
||
| return { | ||
| mockAddVitePlugin, | ||
| getCapturedPlugin: () => capturedPlugin, | ||
| getCapturedPlugin: () => capturedPlugins?.[0] ?? null, | ||
| getCapturedPlugins: () => capturedPlugins, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -46,7 +47,7 @@ | |
| } | ||
|
|
||
| describe('setupSourceMaps hooks', () => { | ||
| const mockSentryVitePlugin = vi.fn(() => ({ name: 'sentry-vite-plugin' })); | ||
| const mockSentryVitePlugin = vi.fn(() => [{ name: 'sentry-vite-plugin' }]); | ||
| const mockSentryRollupPlugin = vi.fn(() => ({ name: 'sentry-rollup-plugin' })); | ||
|
|
||
| const consoleLogSpy = vi.spyOn(console, 'log'); | ||
|
|
@@ -85,95 +86,66 @@ | |
|
|
||
| const plugin = getCapturedPlugin(); | ||
| expect(plugin).not.toBeNull(); | ||
| expect(plugin?.name).toBe('sentry-nuxt-vite-config'); | ||
| // modules:done is called afterward. Later, the plugin is actually added | ||
| expect(plugin?.name).toBe('sentry-nuxt-source-map-validation'); | ||
| }); | ||
|
|
||
| it.each([ | ||
| { | ||
| label: 'prepare mode', | ||
| nuxtOptions: { _prepare: true }, | ||
| viteOptions: { mode: 'production', command: 'build' as const }, | ||
| buildConfig: { build: {}, plugins: [] }, | ||
| }, | ||
| { | ||
| label: 'dev mode', | ||
| nuxtOptions: { dev: true }, | ||
| viteOptions: { mode: 'development', command: 'build' as const }, | ||
| buildConfig: { build: {}, plugins: [] }, | ||
| }, | ||
| ])('does not add plugins to vite config in $label', async ({ nuxtOptions, viteOptions, buildConfig }) => { | ||
| ])('does not add plugins to vite config in $label', async ({ nuxtOptions }) => { | ||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt(nuxtOptions); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| expect(plugin).not.toBeNull(); | ||
|
|
||
| if (plugin && typeof plugin.config === 'function') { | ||
| const viteConfig: UserConfig = buildConfig; | ||
| plugin.config(viteConfig, viteOptions); | ||
| expect(viteConfig.plugins?.length).toBe(0); | ||
| } | ||
| expect(mockAddVitePlugin).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it.each([ | ||
| { label: 'server (SSR) build', buildConfig: { build: { ssr: true }, plugins: [] } }, | ||
| { label: 'client build', buildConfig: { build: { ssr: false }, plugins: [] } }, | ||
| ])('adds sentry vite plugin to vite config for $label in production', async ({ buildConfig }) => { | ||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin, getCapturedPlugins } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| expect(plugin).not.toBeNull(); | ||
|
|
||
| if (plugin && typeof plugin.config === 'function') { | ||
| const viteConfig: UserConfig = buildConfig; | ||
| plugin.config(viteConfig, { mode: 'production', command: 'build' }); | ||
| expect(viteConfig.plugins?.length).toBeGreaterThan(0); | ||
| } | ||
| const plugins = getCapturedPlugins(); | ||
| expect(plugins).not.toBeNull(); | ||
| expect(plugins?.length).toBeGreaterThan(0); | ||
| expect(mockSentryVitePlugin).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('sentry vite plugin calls', () => { | ||
| it('calls sentryVitePlugin in production mode', async () => { | ||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); | ||
| } | ||
|
|
||
| expect(mockSentryVitePlugin).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it.each([ | ||
| { label: 'prepare mode', nuxtOptions: { _prepare: true }, viteMode: 'production' as const }, | ||
| { label: 'dev mode', nuxtOptions: { dev: true }, viteMode: 'development' as const }, | ||
| ])('does not call sentryVitePlugin in $label', async ({ nuxtOptions, viteMode }) => { | ||
| ])('does not call sentryVitePlugin in $label', async ({ nuxtOptions }) => { | ||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt(nuxtOptions); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: {}, plugins: [] }, { mode: viteMode, command: 'build' }); | ||
| } | ||
|
|
||
| expect(mockSentryVitePlugin).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
@@ -187,23 +159,16 @@ | |
| '.*/**/function/**/*.map', | ||
| ]; | ||
|
|
||
| it('uses mutated shouldDeleteFilesFallback (unset → true): plugin.config() after modules:done gets fallback filesToDeleteAfterUpload', async () => { | ||
| it('sentryVitePlugin is called with fallback filesToDeleteAfterUpload when source maps are unset', async () => { | ||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt({ | ||
| _prepare: false, | ||
| dev: false, | ||
| sourcemap: { client: undefined, server: undefined }, | ||
| }); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: false }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| expect(plugin).not.toBeNull(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); | ||
| } | ||
|
|
||
| expect(mockSentryVitePlugin).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
|
|
@@ -214,28 +179,24 @@ | |
| ); | ||
| }); | ||
|
|
||
| it('uses mutated shouldDeleteFilesFallback (explicitly enabled → false): plugin.config() after modules:done gets no filesToDeleteAfterUpload', async () => { | ||
| it('sentryVitePlugin is called with fallback filesToDeleteAfterUpload even when source maps are explicitly enabled', async () => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: Double checking: we do want to delete |
||
| const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); | ||
| const mockNuxt = createMockNuxt({ | ||
| _prepare: false, | ||
| dev: false, | ||
| sourcemap: { client: true, server: true }, | ||
| }); | ||
| const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); | ||
| const { mockAddVitePlugin } = createMockAddVitePlugin(); | ||
|
|
||
| setupSourceMaps({ debug: false }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); | ||
| await mockNuxt.triggerHook('modules:done'); | ||
|
|
||
| const plugin = getCapturedPlugin(); | ||
| expect(plugin).not.toBeNull(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); | ||
| } | ||
|
|
||
| const pluginOptions = (mockSentryVitePlugin?.mock?.calls?.[0] as unknown[])?.[0] as { | ||
| sourcemaps?: { filesToDeleteAfterUpload?: string[] }; | ||
| }; | ||
| expect(pluginOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeUndefined(); | ||
| expect(mockSentryVitePlugin).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| sourcemaps: expect.objectContaining({ | ||
| filesToDeleteAfterUpload: defaultFilesToDeleteAfterUpload, | ||
| }), | ||
| }), | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
|
|
@@ -286,14 +247,14 @@ | |
|
|
||
| const plugin = getCapturedPlugin(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); | ||
| plugin.config({ build: { ssr: false }, plugins: [] } as UserConfig, { mode: 'production', command: 'build' }); | ||
| } | ||
|
|
||
| const nitroConfig = { rollupConfig: { plugins: [] as unknown[], output: {} }, dev: false }; | ||
| await mockNuxt.triggerHook('nitro:config', nitroConfig); | ||
|
|
||
| expect(consoleLogSpy).toHaveBeenCalledWith( | ||
| expect.stringContaining('[Sentry] Adding Sentry Vite plugin to the client runtime.'), | ||
| expect.stringContaining('[Sentry] Validating Vite config for the client runtime.'), | ||
| ); | ||
| expect(consoleLogSpy).toHaveBeenCalledWith( | ||
| expect.stringContaining('[Sentry] Adding Sentry Rollup plugin to the server runtime.'), | ||
|
|
@@ -310,7 +271,7 @@ | |
|
|
||
| const plugin = getCapturedPlugin(); | ||
| if (plugin && typeof plugin.config === 'function') { | ||
| plugin.config({ build: {}, plugins: [] }, { mode: 'production', command: 'build' }); | ||
| plugin.config({ build: {}, plugins: [] } as UserConfig, { mode: 'production', command: 'build' }); | ||
| } | ||
|
|
||
| await mockNuxt.triggerHook('nitro:config', { rollupConfig: { plugins: [] }, dev: false }); | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.