Skip to content

Commit 6e61f82

Browse files
s1gr1dandreiborza
andauthored
fix(nuxt): Add @sentry/nuxt as external in Rollup (#16407)
The Sentry Nuxt SDK emits the Sentry config file during the Rollup build. Users add this file to their application which includes an import from `@sentry/nuxt` and the init function. This file is then added as an `.mjs` file in the build output. However, when building the application, this `.mjs` file included a bunch of OpenTelemetry imports and not only the init code as every dependency from `@sentry/nuxt` was traced back and bundled into the build output. By adding `@sentry/nuxt` as external, the `.mjs` file really only contains the content like in the `.ts` file. **Could** fix #15204 --------- Co-authored-by: Andrei Borza <andrei.borza@sentry.io>
1 parent a788685 commit 6e61f82

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

packages/nuxt/src/vite/addServerConfig.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { SentryNuxtModuleOptions } from '../common/types';
88
import {
99
constructFunctionReExport,
1010
constructWrappedFunctionExportQuery,
11+
getExternalOptionsWithSentryNuxt,
1112
getFilenameFromNodeStartCommand,
1213
QUERY_END_INDICATOR,
1314
removeSentryQueryFromPath,
@@ -130,6 +131,13 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, debug?
130131
return {
131132
name: 'rollup-plugin-inject-sentry-server-config',
132133

134+
options(opts) {
135+
return {
136+
...opts,
137+
external: getExternalOptionsWithSentryNuxt(opts.external),
138+
};
139+
},
140+
133141
buildStart() {
134142
const configPath = createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`);
135143

packages/nuxt/src/vite/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { consoleSandbox } from '@sentry/core';
22
import * as fs from 'fs';
33
import type { Nuxt } from 'nuxt/schema';
44
import * as path from 'path';
5+
import type { ExternalOption } from 'rollup';
56

67
/**
78
* Find the default SDK init file for the given type (client or server).
@@ -72,6 +73,35 @@ export function removeSentryQueryFromPath(url: string): string {
7273
return url.replace(regex, '');
7374
}
7475

76+
/**
77+
* Add @sentry/nuxt to the external options of the Rollup configuration to prevent Rollup bundling all dependencies
78+
* that would result in adding imports from OpenTelemetry libraries etc. to the server build.
79+
*/
80+
export function getExternalOptionsWithSentryNuxt(previousExternal: ExternalOption | undefined): ExternalOption {
81+
const sentryNuxt = /^@sentry\/nuxt$/;
82+
let external: ExternalOption;
83+
84+
if (typeof previousExternal === 'function') {
85+
external = new Proxy(previousExternal, {
86+
apply(target, thisArg, args: [string, string | undefined, boolean]) {
87+
const [source] = args;
88+
if (sentryNuxt.test(source)) {
89+
return true;
90+
}
91+
return Reflect.apply(target, thisArg, args);
92+
},
93+
});
94+
} else if (Array.isArray(previousExternal)) {
95+
external = [sentryNuxt, ...previousExternal];
96+
} else if (previousExternal) {
97+
external = [sentryNuxt, previousExternal];
98+
} else {
99+
external = sentryNuxt;
100+
}
101+
102+
return external;
103+
}
104+
75105
/**
76106
* Extracts and sanitizes function re-export and function wrap query parameters from a query string.
77107
* If it is a default export, it is not considered for re-exporting.

packages/nuxt/test/vite/utils.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
constructWrappedFunctionExportQuery,
77
extractFunctionReexportQueryParameters,
88
findDefaultSdkInitFile,
9+
getExternalOptionsWithSentryNuxt,
910
getFilenameFromNodeStartCommand,
1011
QUERY_END_INDICATOR,
1112
removeSentryQueryFromPath,
@@ -366,3 +367,60 @@ export { foo_sentryWrapped as foo };
366367
expect(result).toBe('');
367368
});
368369
});
370+
371+
describe('getExternalOptionsWithSentryNuxt', () => {
372+
it('should return sentryExternals when previousExternal is undefined', () => {
373+
const result = getExternalOptionsWithSentryNuxt(undefined);
374+
expect(result).toEqual(/^@sentry\/nuxt$/);
375+
});
376+
377+
it('should merge sentryExternals with array previousExternal', () => {
378+
const previousExternal = [/vue/, 'react'];
379+
const result = getExternalOptionsWithSentryNuxt(previousExternal);
380+
expect(result).toEqual([/^@sentry\/nuxt$/, /vue/, 'react']);
381+
});
382+
383+
it('should create array with sentryExternals and non-array previousExternal', () => {
384+
const previousExternal = 'vue';
385+
const result = getExternalOptionsWithSentryNuxt(previousExternal);
386+
expect(result).toEqual([/^@sentry\/nuxt$/, 'vue']);
387+
});
388+
389+
it('should create a proxy when previousExternal is a function', () => {
390+
const mockExternalFn = vi.fn().mockReturnValue(false);
391+
const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
392+
393+
expect(typeof result).toBe('function');
394+
expect(result).toBeInstanceOf(Function);
395+
});
396+
397+
it('should return true from proxied function when source is @sentry/nuxt', () => {
398+
const mockExternalFn = vi.fn().mockReturnValue(false);
399+
const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
400+
401+
// @ts-expect-error - result is a function
402+
const output = result('@sentry/nuxt', undefined, false);
403+
expect(output).toBe(true);
404+
expect(mockExternalFn).not.toHaveBeenCalled();
405+
});
406+
407+
it('should return false from proxied function and call function when source just includes @sentry/nuxt', () => {
408+
const mockExternalFn = vi.fn().mockReturnValue(false);
409+
const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
410+
411+
// @ts-expect-error - result is a function
412+
const output = result('@sentry/nuxt/dist/index.js', undefined, false);
413+
expect(output).toBe(false);
414+
expect(mockExternalFn).toHaveBeenCalledWith('@sentry/nuxt/dist/index.js', undefined, false);
415+
});
416+
417+
it('should call original function when source does not include @sentry/nuxt', () => {
418+
const mockExternalFn = vi.fn().mockReturnValue(false);
419+
const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
420+
421+
// @ts-expect-error - result is a function
422+
const output = result('vue', undefined, false);
423+
expect(output).toBe(false);
424+
expect(mockExternalFn).toHaveBeenCalledWith('vue', undefined, false);
425+
});
426+
});

0 commit comments

Comments
 (0)