diff --git a/packages/svelte/src/config.ts b/packages/svelte/src/config.ts index 4c265ad57fc7..9231ef46426e 100644 --- a/packages/svelte/src/config.ts +++ b/packages/svelte/src/config.ts @@ -22,30 +22,23 @@ export function withSentryConfig( const mergedOptions = { ...DEFAULT_SENTRY_OPTIONS, ...sentryOptions, + componentTracking: { + ...DEFAULT_SENTRY_OPTIONS.componentTracking, + ...(sentryOptions && sentryOptions.componentTracking), + }, }; const originalPreprocessors = getOriginalPreprocessorArray(originalConfig); - // Map is insertion-order-preserving. It's important to add preprocessors - // to this map in the right order we want to see them being executed. - // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map - const sentryPreprocessors = new Map(); - - const shouldTrackComponents = mergedOptions.componentTracking && mergedOptions.componentTracking.trackComponents; - if (shouldTrackComponents) { - const firstPassPreproc: SentryPreprocessorGroup = componentTrackingPreprocessor(mergedOptions.componentTracking); - sentryPreprocessors.set(firstPassPreproc.sentryId || '', firstPassPreproc); + // Bail if users already added the preprocessor + if (originalPreprocessors.find((p: PreprocessorGroup) => !!(p as SentryPreprocessorGroup).sentryId)) { + return originalConfig; } - // We prioritize user-added preprocessors, so we don't insert sentry processors if they - // have already been added by users. - originalPreprocessors.forEach((p: SentryPreprocessorGroup) => { - if (p.sentryId) { - sentryPreprocessors.delete(p.sentryId); - } - }); - - const mergedPreprocessors = [...sentryPreprocessors.values(), ...originalPreprocessors]; + const mergedPreprocessors = [...originalPreprocessors]; + if (mergedOptions.componentTracking.trackComponents) { + mergedPreprocessors.unshift(componentTrackingPreprocessor(mergedOptions.componentTracking)); + } return { ...originalConfig, diff --git a/packages/svelte/src/constants.ts b/packages/svelte/src/constants.ts deleted file mode 100644 index cb8255040c03..000000000000 --- a/packages/svelte/src/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const UI_SVELTE_INIT = 'ui.svelte.init'; - -export const UI_SVELTE_UPDATE = 'ui.svelte.update'; - -export const DEFAULT_COMPONENT_NAME = 'Svelte Component'; diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index b50be258bc58..0c21f8d36622 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -2,8 +2,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; import type { Span } from '@sentry/core'; import { afterUpdate, beforeUpdate, onMount } from 'svelte'; -import { startInactiveSpan } from '@sentry/core'; -import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants'; +import { logger, startInactiveSpan } from '@sentry/core'; import type { TrackComponentOptions } from './types'; const defaultTrackComponentOptions: { @@ -29,21 +28,27 @@ export function trackComponent(options?: TrackComponentOptions): void { const customComponentName = mergedOptions.componentName; - const componentName = `<${customComponentName || DEFAULT_COMPONENT_NAME}>`; + const componentName = `<${customComponentName || 'Svelte Component'}>`; if (mergedOptions.trackInit) { recordInitSpan(componentName); } if (mergedOptions.trackUpdates) { - recordUpdateSpans(componentName); + try { + recordUpdateSpans(componentName); + } catch { + logger.warn( + "Cannot track component updates. This is likely because you're using Svelte 5 in Runes mode. Set `trackUpdates: false` in `withSentryConfig` or `trackComponent` to disable this warning.", + ); + } } } function recordInitSpan(componentName: string): void { const initSpan = startInactiveSpan({ onlyIfParent: true, - op: UI_SVELTE_INIT, + op: 'ui.svelte.init', name: componentName, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.svelte' }, }); @@ -58,7 +63,7 @@ function recordUpdateSpans(componentName: string): void { beforeUpdate(() => { updateSpan = startInactiveSpan({ onlyIfParent: true, - op: UI_SVELTE_UPDATE, + op: 'ui.svelte.update', name: componentName, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.svelte' }, }); diff --git a/packages/svelte/test/config.test.ts b/packages/svelte/test/config.test.ts index a8c84297082a..21f51dc66518 100644 --- a/packages/svelte/test/config.test.ts +++ b/packages/svelte/test/config.test.ts @@ -60,7 +60,7 @@ describe('withSentryConfig', () => { const wrappedConfig = withSentryConfig(originalConfig); - expect(wrappedConfig).toEqual({ ...originalConfig, preprocess: [sentryPreproc] }); + expect(wrappedConfig).toEqual({ ...originalConfig }); }); it('handles multiple wraps correctly by only adding our preprocessors once', () => { diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index 21adeee255c3..67a236116444 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -9,7 +9,6 @@ import { getClient, getCurrentScope, getIsolationScope, init, startSpan } from ' import type { TransactionEvent } from '@sentry/core'; -// @ts-expect-error svelte import import DummyComponent from './components/Dummy.svelte'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -32,7 +31,7 @@ describe('Sentry.trackComponent()', () => { init({ dsn: PUBLIC_DSN, - enableTracing: true, + tracesSampleRate: 1.0, beforeSendTransaction, }); }); @@ -220,7 +219,7 @@ describe('Sentry.trackComponent()', () => { expect(transaction.spans![1]?.description).toEqual(''); }); - it("doesn't do anything, if there's no ongoing transaction", async () => { + it("doesn't do anything, if there's no ongoing parent span", async () => { render(DummyComponent, { props: { options: { componentName: 'CustomComponentName' } }, }); @@ -230,7 +229,7 @@ describe('Sentry.trackComponent()', () => { expect(transactions).toHaveLength(0); }); - it("doesn't record update spans, if there's no ongoing root span at that time", async () => { + it("doesn't record update spans, if there's no ongoing parent span at that time", async () => { const component = startSpan({ name: 'outer' }, span => { expect(span).toBeDefined();