From 0dee42f775bbe2c5b914cd5d1bb5442a57a1ce15 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 19 Aug 2022 18:41:59 +0200 Subject: [PATCH 01/11] feat(svelte): Add Component Tracking --- packages/svelte/package.json | 3 ++ packages/svelte/rollup.npm.config.js | 7 ++- packages/svelte/src/constants.ts | 8 +++ packages/svelte/src/index.ts | 5 ++ packages/svelte/src/performance.ts | 76 ++++++++++++++++++++++++++++ packages/svelte/src/preprocessors.ts | 60 ++++++++++++++++++++++ packages/svelte/src/types.ts | 76 ++++++++++++++++++++++++++++ yarn.lock | 5 ++ 8 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/src/constants.ts create mode 100644 packages/svelte/src/performance.ts create mode 100644 packages/svelte/src/preprocessors.ts create mode 100644 packages/svelte/src/types.ts diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 59d9ae81ea6e..e99591de9c30 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -24,6 +24,9 @@ "peerDependencies": { "svelte": "3.x" }, + "devDependencies": { + "svelte": "3.49.0" + }, "scripts": { "build": "run-p build:rollup build:types", "build:dev": "run-s build", diff --git a/packages/svelte/rollup.npm.config.js b/packages/svelte/rollup.npm.config.js index 5a62b528ef44..ae3c7a9a5b8a 100644 --- a/packages/svelte/rollup.npm.config.js +++ b/packages/svelte/rollup.npm.config.js @@ -1,3 +1,8 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js'; -export default makeNPMConfigVariants(makeBaseNPMConfig()); +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + // Prevent 'svelte/internal' stuff from being included in the built JS + packageSpecificConfig: { external: ['svelte/internal'] }, + }), +); diff --git a/packages/svelte/src/constants.ts b/packages/svelte/src/constants.ts new file mode 100644 index 000000000000..c1783bd02383 --- /dev/null +++ b/packages/svelte/src/constants.ts @@ -0,0 +1,8 @@ +// TODO: we might want to call this ui.svelte.init instead because +// it doesn't only track mounting time (there's no before-/afterMount) +// but component init to mount time. +export const UI_SVELTE_MOUNT = 'ui.svelte.mount'; + +export const UI_SVELTE_UPDATE = 'ui.svelte.update'; + +export const DEFAULT_COMPONENT_NAME = 'Svelte Component'; diff --git a/packages/svelte/src/index.ts b/packages/svelte/src/index.ts index 8e25b84c4a0c..67d17d0e5af6 100644 --- a/packages/svelte/src/index.ts +++ b/packages/svelte/src/index.ts @@ -1,3 +1,8 @@ +export type { ComponentTrackingInitOptions as ComponentTrackingOptions, TrackingOptions } from './types'; + export * from '@sentry/browser'; export { init } from './sdk'; + +export { componentTrackingPreprocessor } from './preprocessors'; +export { trackComponent } from './performance'; diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts new file mode 100644 index 000000000000..28103adb70e9 --- /dev/null +++ b/packages/svelte/src/performance.ts @@ -0,0 +1,76 @@ +import { getCurrentHub } from '@sentry/browser'; +import { Span, Transaction } from '@sentry/types'; +import { afterUpdate, beforeUpdate, onMount } from 'svelte'; +import { current_component } from 'svelte/internal'; + +import { DEFAULT_COMPONENT_NAME, UI_SVELTE_MOUNT, UI_SVELTE_UPDATE } from './constants'; +import { TrackingOptions } from './types'; + +const defaultOptions: TrackingOptions = { + trackMount: true, + trackUpdates: true, +}; + +/** + * Tracks the Svelte component's intialization and mounting operation as well as + * updates and records them as spans. + * This function is injected automatically into your Svelte components' code + * if you are using the Sentry componentTrackingPreprocessor + */ +export function trackComponent(options: TrackingOptions = defaultOptions): void { + const transaction = getActiveTransaction(); + if (!transaction) { + return; + } + + const customComponentName = options && options.componentName; + + // current_component.ctor.name is likely to give us the component's name automatically + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const componentName = `<${customComponentName || current_component.constructor.name || DEFAULT_COMPONENT_NAME}>`; + + options.trackMount && recordMountSpan(transaction, componentName); + options.trackUpdates && recordUpdateSpans(componentName); +} + +function recordMountSpan(transaction: Transaction, componentName: string): void { + const mountSpan = transaction.startChild({ + op: UI_SVELTE_MOUNT, + description: componentName, + }); + + onMount(() => { + mountSpan.finish(); + }); +} + +function recordUpdateSpans(componentName: string): void { + let updateSpan: Span | undefined; + beforeUpdate(() => { + // We need to get the active transaction again because the initial one could + // already be finished or there is no transaction going on, currently. + const transaction = getActiveTransaction(); + if (!transaction) { + return; + } + + updateSpan = transaction.startChild({ + op: UI_SVELTE_UPDATE, + description: componentName, + }); + }); + + afterUpdate(() => { + if (!updateSpan) { + return; + } + updateSpan.finish(); + updateSpan = undefined; + }); +} + +function getActiveTransaction(): Transaction | undefined { + const currentHub = getCurrentHub(); + const scope = currentHub && currentHub.getScope(); + return scope && scope.getTransaction(); +} diff --git a/packages/svelte/src/preprocessors.ts b/packages/svelte/src/preprocessors.ts new file mode 100644 index 000000000000..d93886f80e95 --- /dev/null +++ b/packages/svelte/src/preprocessors.ts @@ -0,0 +1,60 @@ +import { ComponentTrackingInitOptions, PreprocessorGroup, TrackingOptions } from './types'; + +const defaultComponentTrackingOptions: ComponentTrackingInitOptions = { + trackComponents: true, + trackMount: true, + trackUpdates: true, +}; + +/** + * Svelte Preprocessor to inject performance monitoring related code + * into Svelte components. + */ +export function componentTrackingPreprocessor( + options: ComponentTrackingInitOptions = defaultComponentTrackingOptions, +): PreprocessorGroup { + return { + // This script hook is called whenever a Svelte component's