Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ module.exports = [
gzip: true,
disablePlugins: ['@size-limit/esbuild'],
},
{
name: '@sentry/browser (incl. Tracing Core)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingCoreIntegration'),
gzip: true,
disablePlugins: ['@size-limit/esbuild'],
},
{
name: '@sentry/browser (incl. Tracing + Span Streaming)',
path: 'packages/browser/build/npm/esm/prod/index.js',
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export { getFeedback, sendFeedback } from '@sentry/feedback';

export { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './tracing/request';
export {
browserTracingCoreIntegration,
browserTracingIntegration,
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
Expand Down
77 changes: 55 additions & 22 deletions packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
/* eslint-disable max-lines */
import type { Client, IntegrationFn, RequestHookInfo, ResponseHookInfo, Span, StartSpanOptions } from '@sentry/core';
import type {
Client,
Integration,
IntegrationFn,
RequestHookInfo,
ResponseHookInfo,
Span,
StartSpanOptions,
} from '@sentry/core';
import {
addNonEnumerableProperty,
consoleSandbox,
Expand Down Expand Up @@ -39,7 +47,7 @@ import { DEBUG_BUILD } from '../debug-build';
import { filterCollectedUrl } from '@sentry/core';
import { getHttpRequestData, WINDOW } from '../helpers';
import type { WebVitalsOptions } from '../integrations/webVitals';
import { WEB_VITALS_INTEGRATION_NAME, webVitalsIntegration } from '../integrations/webVitals';
import { webVitalsIntegration } from '../integrations/webVitals';
import { registerBackgroundTabDetection } from './backgroundtab';
import { linkTraces } from './linkedTraces';
import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request';
Expand Down Expand Up @@ -274,16 +282,20 @@ export interface BrowserTracingOptions {
onRequestSpanEnd?(span: Span, responseInformation: ResponseHookInfo): void;
}

const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
/**
* Options for `browserTracingCoreIntegration`: the options of `browserTracingIntegration`, minus the ones
* for features that `browserTracingIntegration` registers as standalone integrations.
*/
export type BrowserTracingCoreOptions = Omit<BrowserTracingOptions, 'enableInp' | 'webVitals' | 'enableElementTiming'>;

const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingCoreOptions = {
...TRACING_DEFAULTS,
instrumentNavigation: true,
instrumentBfcacheRestore: true,
instrumentPageLoad: true,
markBackgroundSpan: true,
enableLongTask: true,
enableLongAnimationFrame: true,
// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
enableInp: true,
ignoreResourceSpans: [],
detectRedirects: true,
linkPreviousTrace: 'in-memory',
Expand Down Expand Up @@ -311,18 +323,48 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
});
}

// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
const { enableInp = true, webVitals } = options;

return _browserTracingIntegration(options, client => {
// Custom clients and test doubles may not implement it.
if (!client.addIntegration) {
return;
}

const ignore = webVitals?.ignore ?? [];
client.addIntegration(
webVitalsIntegration({
...webVitals,
ignore: enableInp || ignore.includes('inp') ? ignore : [...ignore, 'inp'],
}),
);
});
}) satisfies IntegrationFn;

/**
* The pageload and navigation spans of `browserTracingIntegration`, without the features it registers as
* standalone integrations (such as `webVitalsIntegration`).
*
* Use it instead of `browserTracingIntegration` to only bundle the integrations you add yourself.
*/
export const browserTracingCoreIntegration = ((options: Partial<BrowserTracingCoreOptions> = {}) =>
_browserTracingIntegration(options)) satisfies IntegrationFn;

function _browserTracingIntegration(
options: Partial<BrowserTracingCoreOptions>,
// Runs before the pageload span starts, so the integrations it adds can hook into it.
addDefaultIntegrations?: (client: Client) => void,
) {
/**
* This is just a small wrapper that makes `document` optional.
* We want to be extra-safe and always check that this exists, to ensure weird environments do not blow up.
*/
const optionalWindowDocument = WINDOW.document as (typeof WINDOW)['document'] | undefined;

const {
// oxlint-disable-next-line typescript/no-deprecated -- still honoured until it is removed
enableInp,
enableLongTask,
enableLongAnimationFrame,
webVitals,
beforeStartSpan,
idleTimeout,
finalTimeout,
Expand Down Expand Up @@ -619,18 +661,9 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
// purely rely on manual startSpan calls.
_INTERNAL_ensureBrowserSpanStreaming(client);

// Auto-register webVitalsIntegration if the user hasn't added one. We do this in
// afterAllSetup so that a user-provided webVitalsIntegration - which may be ordered after
// browserTracingIntegration in the integrations array - has already been installed.
if (client.addIntegration && !client.getIntegrationByName?.(WEB_VITALS_INTEGRATION_NAME)) {
const ignore = webVitals?.ignore ?? [];
client.addIntegration(
webVitalsIntegration({
...webVitals,
ignore: enableInp || ignore.includes('inp') ? ignore : [...ignore, 'inp'],
}),
);
}
// This runs in afterAllSetup so that integrations the user added themselves, which may be
// ordered after this one in the integrations array, are already installed. `addIntegration` skips those.
addDefaultIntegrations?.(client);

let startingUrl: string | undefined = getLocationHref();

Expand Down Expand Up @@ -747,8 +780,8 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
onRequestSpanEnd,
});
},
};
}) satisfies IntegrationFn;
} satisfies Integration;
}

/**
* Manually start a page load span.
Expand Down
21 changes: 17 additions & 4 deletions packages/browser/test/tracing/browserTracingIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vites
import { BrowserClient } from '../../src/client';
import { WINDOW } from '../../src/helpers';
import {
browserTracingCoreIntegration,
browserTracingIntegration,
getServerTiming,
startBrowserTracingNavigationSpan,
Expand Down Expand Up @@ -210,10 +211,22 @@ describe('browserTracingIntegration', () => {
expect(client.getIntegrationByName('WebVitals')).toBeDefined();
});

it('does not auto-register when the user supplies their own webVitalsIntegration', () => {
const webVitalsSpy = vi.spyOn(webVitalsModule, 'webVitalsIntegration');
it('does not register webVitalsIntegration in browserTracingCoreIntegration', () => {
const client = new BrowserClient(
getDefaultBrowserClientOptions({
tracesSampleRate: 1,
integrations: [browserTracingCoreIntegration()],
}),
);
setCurrentClient(client);
client.init();

expect(client.getIntegrationByName('BrowserTracing')).toBeDefined();
expect(client.getIntegrationByName('WebVitals')).toBeUndefined();
});

it('keeps the webVitalsIntegration the user supplies', () => {
const userWebVitals = webVitalsModule.webVitalsIntegration({ softNavigations: false });
webVitalsSpy.mockClear();

const client = new BrowserClient(
getDefaultBrowserClientOptions({
Expand All @@ -224,7 +237,7 @@ describe('browserTracingIntegration', () => {
setCurrentClient(client);
client.init();

expect(webVitalsSpy).not.toHaveBeenCalled();
expect(client.getIntegrationByName('WebVitals')).toBe(userWebVitals);
});

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