From 16bfedcc19dc4ca7cb194836afd029d8c6dbaf2e Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 28 May 2025 11:20:11 +0200 Subject: [PATCH 1/4] fix(nuxt): Add `@sentry/nuxt` as external in Rollup --- packages/nuxt/src/vite/addServerConfig.ts | 8 +++ packages/nuxt/src/vite/utils.ts | 33 +++++++++++ packages/nuxt/test/vite/utils.test.ts | 67 +++++++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index 771c534705cb..83e59d439ecf 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -8,6 +8,7 @@ import type { SentryNuxtModuleOptions } from '../common/types'; import { constructFunctionReExport, constructWrappedFunctionExportQuery, + getExternalOptionsWithSentryNuxt, getFilenameFromNodeStartCommand, QUERY_END_INDICATOR, removeSentryQueryFromPath, @@ -130,6 +131,13 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, debug? return { name: 'rollup-plugin-inject-sentry-server-config', + options(opts) { + return { + ...opts, + external: getExternalOptionsWithSentryNuxt(opts.external), + }; + }, + buildStart() { const configPath = createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`); diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts index ea2db2bc21b8..6e9280310f02 100644 --- a/packages/nuxt/src/vite/utils.ts +++ b/packages/nuxt/src/vite/utils.ts @@ -1,6 +1,8 @@ +import type { Nuxt } from '@nuxt/schema'; import { consoleSandbox } from '@sentry/core'; import * as fs from 'fs'; import * as path from 'path'; +import type { ExternalOption } from 'rollup'; /** * Find the default SDK init file for the given type (client or server). @@ -52,6 +54,37 @@ export function removeSentryQueryFromPath(url: string): string { return url.replace(regex, ''); } +/** + * Add @sentry/nuxt to the external options of the Rollup configuration to prevent Rollup bundling all dependencies + * that would result in adding imports from OpenTelemetry libraries etc. to the server build. + */ +export function getExternalOptionsWithSentryNuxt(previousExternal: ExternalOption | undefined): ExternalOption { + const sentryExternals = [/^@sentry\/nuxt$/]; + let external: ExternalOption; + + if (typeof previousExternal === 'function') { + external = new Proxy(previousExternal, { + apply(target, thisArg, args: [string, string | undefined, boolean]) { + const [source] = args; + if ( + sentryExternals.some(external => (typeof external === 'string' ? source === external : external.test(source))) + ) { + return true; + } + return Reflect.apply(target, thisArg, args); + }, + }); + } else if (Array.isArray(previousExternal)) { + external = [...sentryExternals, ...previousExternal]; + } else if (previousExternal) { + external = [...sentryExternals, previousExternal]; + } else { + external = sentryExternals; + } + + return external; +} + /** * Extracts and sanitizes function re-export and function wrap query parameters from a query string. * If it is a default export, it is not considered for re-exporting. diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts index 7ffd7654549e..ef48fd263140 100644 --- a/packages/nuxt/test/vite/utils.test.ts +++ b/packages/nuxt/test/vite/utils.test.ts @@ -5,6 +5,7 @@ import { constructWrappedFunctionExportQuery, extractFunctionReexportQueryParameters, findDefaultSdkInitFile, + getExternalOptionsWithSentryNuxt, getFilenameFromNodeStartCommand, QUERY_END_INDICATOR, removeSentryQueryFromPath, @@ -296,3 +297,69 @@ export { foo_sentryWrapped as foo }; expect(result).toBe(''); }); }); + +describe('getExternalOptionsWithSentryNuxt', () => { + it('should return sentryExternals when previousExternal is undefined', () => { + const result = getExternalOptionsWithSentryNuxt(undefined); + expect(result).toEqual([/^@sentry\/nuxt$/]); + }); + + it('should merge sentryExternals with array previousExternal', () => { + const previousExternal = [/vue/, 'react']; + const result = getExternalOptionsWithSentryNuxt(previousExternal); + expect(result).toEqual([/^@sentry\/nuxt$/, /vue/, 'react']); + }); + + it('should create array with sentryExternals and non-array previousExternal', () => { + const previousExternal = 'vue'; + const result = getExternalOptionsWithSentryNuxt(previousExternal); + expect(result).toEqual([/^@sentry\/nuxt$/, 'vue']); + }); + + it('should create a proxy when previousExternal is a function', () => { + const mockExternalFn = vi.fn().mockReturnValue(false); + const result = getExternalOptionsWithSentryNuxt(mockExternalFn); + + expect(typeof result).toBe('function'); + expect(result).toBeInstanceOf(Function); + }); + + it('should return true from proxied function when source is @sentry/nuxt', () => { + const mockExternalFn = vi.fn().mockReturnValue(false); + const result = getExternalOptionsWithSentryNuxt(mockExternalFn); + + if (typeof result === 'function') { + const output = result('@sentry/nuxt', undefined, false); + expect(output).toBe(true); + expect(mockExternalFn).not.toHaveBeenCalled(); + } else { + throw Error('Result should be a function'); + } + }); + + it('should return false from proxied function and call function when source just includes @sentry/nuxt', () => { + const mockExternalFn = vi.fn().mockReturnValue(false); + const result = getExternalOptionsWithSentryNuxt(mockExternalFn); + + if (typeof result === 'function') { + const output = result('@sentry/nuxt/dist/index.js', undefined, false); + expect(output).toBe(false); + expect(mockExternalFn).toHaveBeenCalledWith('@sentry/nuxt/dist/index.js', undefined, false); + } else { + throw Error('Result should be a function'); + } + }); + + it('should call original function when source does not include @sentry/nuxt', () => { + const mockExternalFn = vi.fn().mockReturnValue(false); + const result = getExternalOptionsWithSentryNuxt(mockExternalFn); + + if (typeof result === 'function') { + const output = result('vue', undefined, false); + expect(output).toBe(false); + expect(mockExternalFn).toHaveBeenCalledWith('vue', undefined, false); + } else { + throw Error('Result should be a function'); + } + }); +}); From 67a5e2d7da4911dbca7125338eab44067286c54d Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 28 May 2025 11:29:52 +0200 Subject: [PATCH 2/4] remove nuxt import --- packages/nuxt/src/vite/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts index 6e9280310f02..bf414ef75584 100644 --- a/packages/nuxt/src/vite/utils.ts +++ b/packages/nuxt/src/vite/utils.ts @@ -1,4 +1,3 @@ -import type { Nuxt } from '@nuxt/schema'; import { consoleSandbox } from '@sentry/core'; import * as fs from 'fs'; import * as path from 'path'; From 41b15f524f189a920b8510cbb65eb393747e7243 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 28 May 2025 12:17:24 +0200 Subject: [PATCH 3/4] simplify if condition --- packages/nuxt/src/vite/utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts index bf414ef75584..3e1fdc330927 100644 --- a/packages/nuxt/src/vite/utils.ts +++ b/packages/nuxt/src/vite/utils.ts @@ -65,9 +65,7 @@ export function getExternalOptionsWithSentryNuxt(previousExternal: ExternalOptio external = new Proxy(previousExternal, { apply(target, thisArg, args: [string, string | undefined, boolean]) { const [source] = args; - if ( - sentryExternals.some(external => (typeof external === 'string' ? source === external : external.test(source))) - ) { + if (sentryExternals.some(external => external.test(source))) { return true; } return Reflect.apply(target, thisArg, args); From b70ea46832a681cded4b453ce591bb9ad5e81e0b Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 28 May 2025 13:31:55 +0200 Subject: [PATCH 4/4] Simplify logic --- packages/nuxt/src/vite/utils.ts | 10 ++++---- packages/nuxt/test/vite/utils.test.ts | 35 ++++++++++----------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts index 3e1fdc330927..1aae2b7ea2ba 100644 --- a/packages/nuxt/src/vite/utils.ts +++ b/packages/nuxt/src/vite/utils.ts @@ -58,25 +58,25 @@ export function removeSentryQueryFromPath(url: string): string { * that would result in adding imports from OpenTelemetry libraries etc. to the server build. */ export function getExternalOptionsWithSentryNuxt(previousExternal: ExternalOption | undefined): ExternalOption { - const sentryExternals = [/^@sentry\/nuxt$/]; + const sentryNuxt = /^@sentry\/nuxt$/; let external: ExternalOption; if (typeof previousExternal === 'function') { external = new Proxy(previousExternal, { apply(target, thisArg, args: [string, string | undefined, boolean]) { const [source] = args; - if (sentryExternals.some(external => external.test(source))) { + if (sentryNuxt.test(source)) { return true; } return Reflect.apply(target, thisArg, args); }, }); } else if (Array.isArray(previousExternal)) { - external = [...sentryExternals, ...previousExternal]; + external = [sentryNuxt, ...previousExternal]; } else if (previousExternal) { - external = [...sentryExternals, previousExternal]; + external = [sentryNuxt, previousExternal]; } else { - external = sentryExternals; + external = sentryNuxt; } return external; diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts index ef48fd263140..452c2780721f 100644 --- a/packages/nuxt/test/vite/utils.test.ts +++ b/packages/nuxt/test/vite/utils.test.ts @@ -301,7 +301,7 @@ export { foo_sentryWrapped as foo }; describe('getExternalOptionsWithSentryNuxt', () => { it('should return sentryExternals when previousExternal is undefined', () => { const result = getExternalOptionsWithSentryNuxt(undefined); - expect(result).toEqual([/^@sentry\/nuxt$/]); + expect(result).toEqual(/^@sentry\/nuxt$/); }); it('should merge sentryExternals with array previousExternal', () => { @@ -328,38 +328,29 @@ describe('getExternalOptionsWithSentryNuxt', () => { const mockExternalFn = vi.fn().mockReturnValue(false); const result = getExternalOptionsWithSentryNuxt(mockExternalFn); - if (typeof result === 'function') { - const output = result('@sentry/nuxt', undefined, false); - expect(output).toBe(true); - expect(mockExternalFn).not.toHaveBeenCalled(); - } else { - throw Error('Result should be a function'); - } + // @ts-expect-error - result is a function + const output = result('@sentry/nuxt', undefined, false); + expect(output).toBe(true); + expect(mockExternalFn).not.toHaveBeenCalled(); }); it('should return false from proxied function and call function when source just includes @sentry/nuxt', () => { const mockExternalFn = vi.fn().mockReturnValue(false); const result = getExternalOptionsWithSentryNuxt(mockExternalFn); - if (typeof result === 'function') { - const output = result('@sentry/nuxt/dist/index.js', undefined, false); - expect(output).toBe(false); - expect(mockExternalFn).toHaveBeenCalledWith('@sentry/nuxt/dist/index.js', undefined, false); - } else { - throw Error('Result should be a function'); - } + // @ts-expect-error - result is a function + const output = result('@sentry/nuxt/dist/index.js', undefined, false); + expect(output).toBe(false); + expect(mockExternalFn).toHaveBeenCalledWith('@sentry/nuxt/dist/index.js', undefined, false); }); it('should call original function when source does not include @sentry/nuxt', () => { const mockExternalFn = vi.fn().mockReturnValue(false); const result = getExternalOptionsWithSentryNuxt(mockExternalFn); - if (typeof result === 'function') { - const output = result('vue', undefined, false); - expect(output).toBe(false); - expect(mockExternalFn).toHaveBeenCalledWith('vue', undefined, false); - } else { - throw Error('Result should be a function'); - } + // @ts-expect-error - result is a function + const output = result('vue', undefined, false); + expect(output).toBe(false); + expect(mockExternalFn).toHaveBeenCalledWith('vue', undefined, false); }); });