Skip to content

Commit fdcec31

Browse files
committed
feat(browser): Add browserTracingCoreIntegration
`browserTracingIntegration` statically imports the integrations it registers by default, so bundlers can never drop them. `browserTracingCoreIntegration` is the same integration without them, for users who want to add only the parts they need.
1 parent 9face8a commit fdcec31

4 files changed

Lines changed: 80 additions & 26 deletions

File tree

‎.size-limit.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ module.exports = [
6464
gzip: true,
6565
disablePlugins: ['@size-limit/esbuild'],
6666
},
67+
{
68+
name: '@sentry/browser (incl. Tracing Core)',
69+
path: 'packages/browser/build/npm/esm/prod/index.js',
70+
import: createImport('init', 'browserTracingCoreIntegration'),
71+
gzip: true,
72+
disablePlugins: ['@size-limit/esbuild'],
73+
},
6774
{
6875
name: '@sentry/browser (incl. Tracing + Span Streaming)',
6976
path: 'packages/browser/build/npm/esm/prod/index.js',

‎packages/browser/src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export { getFeedback, sendFeedback } from '@sentry/feedback';
2828

2929
export { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './tracing/request';
3030
export {
31+
browserTracingCoreIntegration,
3132
browserTracingIntegration,
3233
startBrowserTracingNavigationSpan,
3334
startBrowserTracingPageLoadSpan,

‎packages/browser/src/tracing/browserTracingIntegration.ts‎

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
/* eslint-disable max-lines */
2-
import type { Client, IntegrationFn, RequestHookInfo, ResponseHookInfo, Span, StartSpanOptions } from '@sentry/core';
2+
import type {
3+
Client,
4+
Integration,
5+
IntegrationFn,
6+
RequestHookInfo,
7+
ResponseHookInfo,
8+
Span,
9+
StartSpanOptions,
10+
} from '@sentry/core';
311
import {
412
addNonEnumerableProperty,
513
consoleSandbox,
@@ -39,7 +47,7 @@ import { DEBUG_BUILD } from '../debug-build';
3947
import { filterCollectedUrl } from '@sentry/core';
4048
import { getHttpRequestData, WINDOW } from '../helpers';
4149
import type { WebVitalsOptions } from '../integrations/webVitals';
42-
import { WEB_VITALS_INTEGRATION_NAME, webVitalsIntegration } from '../integrations/webVitals';
50+
import { webVitalsIntegration } from '../integrations/webVitals';
4351
import { registerBackgroundTabDetection } from './backgroundtab';
4452
import { linkTraces } from './linkedTraces';
4553
import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request';
@@ -274,16 +282,20 @@ export interface BrowserTracingOptions {
274282
onRequestSpanEnd?(span: Span, responseInformation: ResponseHookInfo): void;
275283
}
276284

277-
const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
285+
/**
286+
* Options for `browserTracingCoreIntegration`: the options of `browserTracingIntegration`, minus the ones
287+
* for features that `browserTracingIntegration` registers as standalone integrations.
288+
*/
289+
export type BrowserTracingCoreOptions = Omit<BrowserTracingOptions, 'enableInp' | 'webVitals' | 'enableElementTiming'>;
290+
291+
const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingCoreOptions = {
278292
...TRACING_DEFAULTS,
279293
instrumentNavigation: true,
280294
instrumentBfcacheRestore: true,
281295
instrumentPageLoad: true,
282296
markBackgroundSpan: true,
283297
enableLongTask: true,
284298
enableLongAnimationFrame: true,
285-
// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
286-
enableInp: true,
287299
ignoreResourceSpans: [],
288300
detectRedirects: true,
289301
linkPreviousTrace: 'in-memory',
@@ -311,18 +323,48 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
311323
});
312324
}
313325

326+
// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
327+
const { enableInp = true, webVitals } = options;
328+
329+
return _browserTracingIntegration(options, client => {
330+
// Custom clients and test doubles may not implement it.
331+
if (!client.addIntegration) {
332+
return;
333+
}
334+
335+
const ignore = webVitals?.ignore ?? [];
336+
client.addIntegration(
337+
webVitalsIntegration({
338+
...webVitals,
339+
ignore: enableInp || ignore.includes('inp') ? ignore : [...ignore, 'inp'],
340+
}),
341+
);
342+
});
343+
}) satisfies IntegrationFn;
344+
345+
/**
346+
* The pageload and navigation spans of `browserTracingIntegration`, without the features it registers as
347+
* standalone integrations (such as `webVitalsIntegration`).
348+
*
349+
* Use it instead of `browserTracingIntegration` to only bundle the integrations you add yourself.
350+
*/
351+
export const browserTracingCoreIntegration = ((options: Partial<BrowserTracingCoreOptions> = {}) =>
352+
_browserTracingIntegration(options)) satisfies IntegrationFn;
353+
354+
function _browserTracingIntegration(
355+
options: Partial<BrowserTracingCoreOptions>,
356+
// Runs before the pageload span starts, so the integrations it adds can hook into it.
357+
addDefaultIntegrations?: (client: Client) => void,
358+
) {
314359
/**
315360
* This is just a small wrapper that makes `document` optional.
316361
* We want to be extra-safe and always check that this exists, to ensure weird environments do not blow up.
317362
*/
318363
const optionalWindowDocument = WINDOW.document as (typeof WINDOW)['document'] | undefined;
319364

320365
const {
321-
// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
322-
enableInp,
323366
enableLongTask,
324367
enableLongAnimationFrame,
325-
webVitals,
326368
beforeStartSpan,
327369
idleTimeout,
328370
finalTimeout,
@@ -619,18 +661,9 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
619661
// purely rely on manual startSpan calls.
620662
_INTERNAL_ensureBrowserSpanStreaming(client);
621663

622-
// Auto-register webVitalsIntegration if the user hasn't added one. We do this in
623-
// afterAllSetup so that a user-provided webVitalsIntegration - which may be ordered after
624-
// browserTracingIntegration in the integrations array - has already been installed.
625-
if (client.addIntegration && !client.getIntegrationByName?.(WEB_VITALS_INTEGRATION_NAME)) {
626-
const ignore = webVitals?.ignore ?? [];
627-
client.addIntegration(
628-
webVitalsIntegration({
629-
...webVitals,
630-
ignore: enableInp || ignore.includes('inp') ? ignore : [...ignore, 'inp'],
631-
}),
632-
);
633-
}
664+
// This runs in afterAllSetup so that integrations the user added themselves, which may be
665+
// ordered after this one in the integrations array, are already installed. `addIntegration` skips those.
666+
addDefaultIntegrations?.(client);
634667

635668
let startingUrl: string | undefined = getLocationHref();
636669

@@ -747,8 +780,8 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
747780
onRequestSpanEnd,
748781
});
749782
},
750-
};
751-
}) satisfies IntegrationFn;
783+
} satisfies Integration;
784+
}
752785

753786
/**
754787
* Manually start a page load span.

‎packages/browser/test/tracing/browserTracingIntegration.test.ts‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vites
2424
import { BrowserClient } from '../../src/client';
2525
import { WINDOW } from '../../src/helpers';
2626
import {
27+
browserTracingCoreIntegration,
2728
browserTracingIntegration,
2829
getServerTiming,
2930
startBrowserTracingNavigationSpan,
@@ -210,10 +211,22 @@ describe('browserTracingIntegration', () => {
210211
expect(client.getIntegrationByName('WebVitals')).toBeDefined();
211212
});
212213

213-
it('does not auto-register when the user supplies their own webVitalsIntegration', () => {
214-
const webVitalsSpy = vi.spyOn(webVitalsModule, 'webVitalsIntegration');
214+
it('does not register webVitalsIntegration in browserTracingCoreIntegration', () => {
215+
const client = new BrowserClient(
216+
getDefaultBrowserClientOptions({
217+
tracesSampleRate: 1,
218+
integrations: [browserTracingCoreIntegration()],
219+
}),
220+
);
221+
setCurrentClient(client);
222+
client.init();
223+
224+
expect(client.getIntegrationByName('BrowserTracing')).toBeDefined();
225+
expect(client.getIntegrationByName('WebVitals')).toBeUndefined();
226+
});
227+
228+
it('keeps the webVitalsIntegration the user supplies', () => {
215229
const userWebVitals = webVitalsModule.webVitalsIntegration({ softNavigations: false });
216-
webVitalsSpy.mockClear();
217230

218231
const client = new BrowserClient(
219232
getDefaultBrowserClientOptions({
@@ -224,7 +237,7 @@ describe('browserTracingIntegration', () => {
224237
setCurrentClient(client);
225238
client.init();
226239

227-
expect(webVitalsSpy).not.toHaveBeenCalled();
240+
expect(client.getIntegrationByName('WebVitals')).toBe(userWebVitals);
228241
});
229242

230243
it('forwards webVitals options to the auto-registered integration', () => {

0 commit comments

Comments
 (0)