Skip to content

Commit 1136d87

Browse files
Lms24Zen-cronic
authored andcommitted
feat(svelte)!: Disable component update tracking by default (getsentry#15265)
Due to a change in the lifecycle of Svelte components in Svelte 5 (using Rune mode), our SDK can no longer leverage the `(before|after)Update` hooks to track component update spans. For v9, this patch therefore disables update tracking by default.
1 parent 68f7d1a commit 1136d87

File tree

13 files changed

+57
-95
lines changed

13 files changed

+57
-95
lines changed

dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.client.test.ts

-24
Original file line numberDiff line numberDiff line change
@@ -109,30 +109,6 @@ test.describe('client-specific performance events', () => {
109109
op: 'ui.svelte.init',
110110
origin: 'auto.ui.svelte',
111111
}),
112-
expect.objectContaining({
113-
data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' },
114-
description: '<components/+page>',
115-
op: 'ui.svelte.update',
116-
origin: 'auto.ui.svelte',
117-
}),
118-
expect.objectContaining({
119-
data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' },
120-
description: '<Component1>',
121-
op: 'ui.svelte.update',
122-
origin: 'auto.ui.svelte',
123-
}),
124-
expect.objectContaining({
125-
data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' },
126-
description: '<Component2>',
127-
op: 'ui.svelte.update',
128-
origin: 'auto.ui.svelte',
129-
}),
130-
expect.objectContaining({
131-
data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' },
132-
description: '<Component3>',
133-
op: 'ui.svelte.update',
134-
origin: 'auto.ui.svelte',
135-
}),
136112
]),
137113
);
138114
});

dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/+page.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import Component2 from "./Component2.svelte";
66
import Component3 from "./Component3.svelte";
77
8-
Sentry.trackComponent({componentName: 'components/+page'})
8+
Sentry.trackComponent({componentName: 'components/+page', trackUpdates: true})
99
1010
</script>
1111
<h2>Demonstrating Component Tracking</h2>

dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component1.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import Component2 from "./Component2.svelte";
33
import {trackComponent} from '@sentry/sveltekit';
44
5-
trackComponent({componentName: 'Component1'});
5+
trackComponent({componentName: 'Component1', trackUpdates: true});
66
77
</script>
88
<h3>Howdy, I'm component 1</h3>

dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component2.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import Component3 from "./Component3.svelte";
33
import {trackComponent} from '@sentry/sveltekit';
44
5-
trackComponent({componentName: 'Component2'});
5+
trackComponent({componentName: 'Component2', trackUpdates: true});
66
</script>
77
<h3>Howdy, I'm component 2</h3>
88

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import * as Sentry from '@sentry/sveltekit';
3-
Sentry.trackComponent({componentName: 'Component3'});
3+
Sentry.trackComponent({componentName: 'Component3', trackUpdates: true});
44
</script>
55

66
<h3>Howdy, I'm component 3</h3>

packages/svelte/src/config.ts

+13-20
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
33
import { componentTrackingPreprocessor, defaultComponentTrackingOptions } from './preprocessors';
44
import type { SentryPreprocessorGroup, SentrySvelteConfigOptions, SvelteConfig } from './types';
55

6-
const DEFAULT_SENTRY_OPTIONS: SentrySvelteConfigOptions = {
6+
const defaultSentryOptions: SentrySvelteConfigOptions = {
77
componentTracking: defaultComponentTrackingOptions,
88
};
99

@@ -20,32 +20,25 @@ export function withSentryConfig(
2020
sentryOptions?: SentrySvelteConfigOptions,
2121
): SvelteConfig {
2222
const mergedOptions = {
23-
...DEFAULT_SENTRY_OPTIONS,
23+
...defaultSentryOptions,
2424
...sentryOptions,
25+
componentTracking: {
26+
...defaultSentryOptions.componentTracking,
27+
...sentryOptions?.componentTracking,
28+
},
2529
};
2630

2731
const originalPreprocessors = getOriginalPreprocessorArray(originalConfig);
2832

29-
// Map is insertion-order-preserving. It's important to add preprocessors
30-
// to this map in the right order we want to see them being executed.
31-
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
32-
const sentryPreprocessors = new Map<string, SentryPreprocessorGroup>();
33-
34-
const shouldTrackComponents = mergedOptions.componentTracking?.trackComponents;
35-
if (shouldTrackComponents) {
36-
const firstPassPreproc: SentryPreprocessorGroup = componentTrackingPreprocessor(mergedOptions.componentTracking);
37-
sentryPreprocessors.set(firstPassPreproc.sentryId || '', firstPassPreproc);
33+
// Bail if users already added the preprocessor
34+
if (originalPreprocessors.find((p: PreprocessorGroup) => !!(p as SentryPreprocessorGroup).sentryId)) {
35+
return originalConfig;
3836
}
3937

40-
// We prioritize user-added preprocessors, so we don't insert sentry processors if they
41-
// have already been added by users.
42-
originalPreprocessors.forEach((p: SentryPreprocessorGroup) => {
43-
if (p.sentryId) {
44-
sentryPreprocessors.delete(p.sentryId);
45-
}
46-
});
47-
48-
const mergedPreprocessors = [...sentryPreprocessors.values(), ...originalPreprocessors];
38+
const mergedPreprocessors = [...originalPreprocessors];
39+
if (mergedOptions.componentTracking.trackComponents) {
40+
mergedPreprocessors.unshift(componentTrackingPreprocessor(mergedOptions.componentTracking));
41+
}
4942

5043
return {
5144
...originalConfig,

packages/svelte/src/constants.ts

-5
This file was deleted.

packages/svelte/src/performance.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser';
22
import type { Span } from '@sentry/core';
33
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
44

5-
import { startInactiveSpan } from '@sentry/core';
6-
import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants';
5+
import { logger, startInactiveSpan } from '@sentry/core';
76
import type { TrackComponentOptions } from './types';
87

98
const defaultTrackComponentOptions: {
@@ -12,7 +11,7 @@ const defaultTrackComponentOptions: {
1211
componentName?: string;
1312
} = {
1413
trackInit: true,
15-
trackUpdates: true,
14+
trackUpdates: false,
1615
};
1716

1817
/**
@@ -29,21 +28,27 @@ export function trackComponent(options?: TrackComponentOptions): void {
2928

3029
const customComponentName = mergedOptions.componentName;
3130

32-
const componentName = `<${customComponentName || DEFAULT_COMPONENT_NAME}>`;
31+
const componentName = `<${customComponentName || 'Svelte Component'}>`;
3332

3433
if (mergedOptions.trackInit) {
3534
recordInitSpan(componentName);
3635
}
3736

3837
if (mergedOptions.trackUpdates) {
39-
recordUpdateSpans(componentName);
38+
try {
39+
recordUpdateSpans(componentName);
40+
} catch {
41+
logger.warn(
42+
"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.",
43+
);
44+
}
4045
}
4146
}
4247

4348
function recordInitSpan(componentName: string): void {
4449
const initSpan = startInactiveSpan({
4550
onlyIfParent: true,
46-
op: UI_SVELTE_INIT,
51+
op: 'ui.svelte.init',
4752
name: componentName,
4853
attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.svelte' },
4954
});
@@ -58,7 +63,7 @@ function recordUpdateSpans(componentName: string): void {
5863
beforeUpdate(() => {
5964
updateSpan = startInactiveSpan({
6065
onlyIfParent: true,
61-
op: UI_SVELTE_UPDATE,
66+
op: 'ui.svelte.update',
6267
name: componentName,
6368
attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.svelte' },
6469
});

packages/svelte/src/preprocessors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { ComponentTrackingInitOptions, SentryPreprocessorGroup, TrackCompon
66
export const defaultComponentTrackingOptions: Required<ComponentTrackingInitOptions> = {
77
trackComponents: true,
88
trackInit: true,
9-
trackUpdates: true,
9+
trackUpdates: false,
1010
};
1111

1212
export const FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID = 'FIRST_PASS_COMPONENT_TRACKING_PREPROCESSOR';

packages/svelte/src/types.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ export type SpanOptions = {
2929
* onMount lifecycle hook. This span tells how long it takes a component
3030
* to be created and inserted into the DOM.
3131
*
32-
* Defaults to true if component tracking is enabled
32+
* @default `true` if component tracking is enabled
3333
*/
3434
trackInit?: boolean;
3535

3636
/**
3737
* If true, a span is recorded between a component's beforeUpdate and afterUpdate
3838
* lifecycle hooks.
3939
*
40-
* Defaults to true if component tracking is enabled
40+
* Caution: Component updates can only be tracked in Svelte versions prior to version 5
41+
* or in Svelte 5 in legacy mode (i.e. without Runes).
42+
*
43+
* @default `false` if component tracking is enabled
4144
*/
4245
trackUpdates?: boolean;
4346
};

packages/svelte/test/config.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe('withSentryConfig', () => {
6060

6161
const wrappedConfig = withSentryConfig(originalConfig);
6262

63-
expect(wrappedConfig).toEqual({ ...originalConfig, preprocess: [sentryPreproc] });
63+
expect(wrappedConfig).toEqual({ ...originalConfig });
6464
});
6565

6666
it('handles multiple wraps correctly by only adding our preprocessors once', () => {

packages/svelte/test/performance.test.ts

+15-25
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { getClient, getCurrentScope, getIsolationScope, init, startSpan } from '
99

1010
import type { TransactionEvent } from '@sentry/core';
1111

12-
// @ts-expect-error svelte import
1312
import DummyComponent from './components/Dummy.svelte';
1413

1514
const PUBLIC_DSN = 'https://username@domain/123';
@@ -37,7 +36,7 @@ describe('Sentry.trackComponent()', () => {
3736
});
3837
});
3938

40-
it('creates init and update spans on component initialization', async () => {
39+
it('creates init spans on component initialization by default', async () => {
4140
startSpan({ name: 'outer' }, span => {
4241
expect(span).toBeDefined();
4342
render(DummyComponent, { props: { options: {} } });
@@ -47,7 +46,7 @@ describe('Sentry.trackComponent()', () => {
4746

4847
expect(transactions).toHaveLength(1);
4948
const transaction = transactions[0]!;
50-
expect(transaction.spans).toHaveLength(2);
49+
expect(transaction.spans).toHaveLength(1);
5150

5251
const rootSpanId = transaction.contexts?.trace?.span_id;
5352
expect(rootSpanId).toBeDefined();
@@ -68,29 +67,14 @@ describe('Sentry.trackComponent()', () => {
6867
timestamp: expect.any(Number),
6968
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
7069
});
71-
72-
expect(transaction.spans![1]).toEqual({
73-
data: {
74-
'sentry.op': 'ui.svelte.update',
75-
'sentry.origin': 'auto.ui.svelte',
76-
},
77-
description: '<Svelte Component>',
78-
op: 'ui.svelte.update',
79-
origin: 'auto.ui.svelte',
80-
parent_span_id: rootSpanId,
81-
span_id: expect.stringMatching(/[a-f0-9]{16}/),
82-
start_timestamp: expect.any(Number),
83-
timestamp: expect.any(Number),
84-
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
85-
});
8670
});
8771

88-
it('creates an update span, when the component is updated', async () => {
72+
it('creates an update span, if `trackUpdates` is `true`', async () => {
8973
startSpan({ name: 'outer' }, async span => {
9074
expect(span).toBeDefined();
9175

9276
// first we create the component
93-
const { component } = render(DummyComponent, { props: { options: {} } });
77+
const { component } = render(DummyComponent, { props: { options: { trackUpdates: true } } });
9478

9579
// then trigger an update
9680
// (just changing the trackUpdates prop so that we trigger an update. #
@@ -175,7 +159,7 @@ describe('Sentry.trackComponent()', () => {
175159
startSpan({ name: 'outer' }, span => {
176160
expect(span).toBeDefined();
177161

178-
render(DummyComponent, { props: { options: { trackInit: false } } });
162+
render(DummyComponent, { props: { options: { trackInit: false, trackUpdates: true } } });
179163
});
180164

181165
await getClient()?.flush();
@@ -206,7 +190,13 @@ describe('Sentry.trackComponent()', () => {
206190
expect(span).toBeDefined();
207191

208192
render(DummyComponent, {
209-
props: { options: { componentName: 'CustomComponentName' } },
193+
props: {
194+
options: {
195+
componentName: 'CustomComponentName',
196+
// enabling updates to check for both span names in one test
197+
trackUpdates: true,
198+
},
199+
},
210200
});
211201
});
212202

@@ -220,7 +210,7 @@ describe('Sentry.trackComponent()', () => {
220210
expect(transaction.spans![1]?.description).toEqual('<CustomComponentName>');
221211
});
222212

223-
it("doesn't do anything, if there's no ongoing transaction", async () => {
213+
it("doesn't do anything, if there's no ongoing parent span", async () => {
224214
render(DummyComponent, {
225215
props: { options: { componentName: 'CustomComponentName' } },
226216
});
@@ -230,11 +220,11 @@ describe('Sentry.trackComponent()', () => {
230220
expect(transactions).toHaveLength(0);
231221
});
232222

233-
it("doesn't record update spans, if there's no ongoing root span at that time", async () => {
223+
it("doesn't record update spans, if there's no ongoing parent span at that time", async () => {
234224
const component = startSpan({ name: 'outer' }, span => {
235225
expect(span).toBeDefined();
236226

237-
const { component } = render(DummyComponent, { props: { options: {} } });
227+
const { component } = render(DummyComponent, { props: { options: { trackUpdates: true } } });
238228
return component;
239229
});
240230

packages/svelte/test/preprocessors.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function expectComponentCodeToBeModified(
2424
preprocessedComponents.forEach(cmp => {
2525
const expectedFunctionCallOptions = {
2626
trackInit: options?.trackInit ?? true,
27-
trackUpdates: options?.trackUpdates ?? true,
27+
trackUpdates: options?.trackUpdates ?? false,
2828
componentName: cmp.name,
2929
};
3030
const expectedFunctionCall = `trackComponent(${JSON.stringify(expectedFunctionCallOptions)});\n`;
@@ -115,7 +115,7 @@ describe('componentTrackingPreprocessor', () => {
115115

116116
expect(cmp2?.newCode).toEqual(cmp2?.originalCode);
117117

118-
expectComponentCodeToBeModified([cmp1!, cmp3!], { trackInit: true, trackUpdates: true });
118+
expectComponentCodeToBeModified([cmp1!, cmp3!], { trackInit: true, trackUpdates: false });
119119
});
120120

121121
it('doesnt inject the function call to the same component more than once', () => {
@@ -149,7 +149,7 @@ describe('componentTrackingPreprocessor', () => {
149149
return { ...cmp, newCode: res.code, map: res.map };
150150
});
151151

152-
expectComponentCodeToBeModified([cmp11!, cmp2!], { trackInit: true, trackUpdates: true });
152+
expectComponentCodeToBeModified([cmp11!, cmp2!], { trackInit: true });
153153
expect(cmp12!.newCode).toEqual(cmp12!.originalCode);
154154
});
155155

@@ -228,7 +228,7 @@ describe('componentTrackingPreprocessor', () => {
228228

229229
expect(processedCode.code).toEqual(
230230
'<script>import { trackComponent } from "@sentry/svelte";\n' +
231-
'trackComponent({"trackInit":true,"trackUpdates":true,"componentName":"Cmp1"});\n\n' +
231+
'trackComponent({"trackInit":true,"trackUpdates":false,"componentName":"Cmp1"});\n\n' +
232232
'</script>\n' +
233233
"<p>I'm just a plain component</p>\n" +
234234
'<style>p{margin-top:10px}</style>',
@@ -248,7 +248,7 @@ describe('componentTrackingPreprocessor', () => {
248248

249249
expect(processedCode.code).toEqual(
250250
'<script>import { trackComponent } from "@sentry/svelte";\n' +
251-
'trackComponent({"trackInit":true,"trackUpdates":true,"componentName":"Cmp2"});\n' +
251+
'trackComponent({"trackInit":true,"trackUpdates":false,"componentName":"Cmp2"});\n' +
252252
"console.log('hi');</script>\n" +
253253
"<p>I'm a component with a script</p>\n" +
254254
'<style>p{margin-top:10px}</style>',
@@ -267,7 +267,7 @@ describe('componentTrackingPreprocessor', () => {
267267

268268
expect(processedCode.code).toEqual(
269269
'<script>import { trackComponent } from "@sentry/svelte";\n' +
270-
'trackComponent({"trackInit":true,"trackUpdates":true,"componentName":"unknown"});\n' +
270+
'trackComponent({"trackInit":true,"trackUpdates":false,"componentName":"unknown"});\n' +
271271
"console.log('hi');</script>",
272272
);
273273
});

0 commit comments

Comments
 (0)