From 1d27eb474d9055fcb01e6116bc83a62b3a118f4b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 21 Aug 2026 11:38:47 +0200 Subject: [PATCH] feat(node): Add build-time opt-out for runtime channel injection Introduce a `__SENTRY_CHANNEL_INJECTION__` treeshaking flag (mirroring `__SENTRY_TRACING__`) that removes the runtime diagnostics-channel injection when text-replaced with `false`, and expose it through the bundler plugins' `bundleSizeOptimizations.excludeChannelInjection`. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/core/build-plugin-manager.ts | 3 +++ .../bundler-plugins/src/core/options-mapping.ts | 1 + packages/bundler-plugins/src/core/types.ts | 16 ++++++++++++++++ packages/node/src/sdk/index.ts | 15 +++++++++++---- .../test/sdk/diagnosticsChannelInjection.test.ts | 14 ++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts index 1e2d2b6bad50..b44c8203b242 100644 --- a/packages/bundler-plugins/src/core/build-plugin-manager.ts +++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts @@ -335,6 +335,9 @@ export function createSentryBuildPluginManager( if (bundleSizeOptimizations.excludeTracing) { bundleSizeOptimizationReplacementValues['__SENTRY_TRACING__'] = false; } + if (bundleSizeOptimizations.excludeChannelInjection) { + bundleSizeOptimizationReplacementValues['__SENTRY_CHANNEL_INJECTION__'] = false; + } if (bundleSizeOptimizations.excludeReplayCanvas) { bundleSizeOptimizationReplacementValues['__RRWEB_EXCLUDE_CANVAS__'] = true; } diff --git a/packages/bundler-plugins/src/core/options-mapping.ts b/packages/bundler-plugins/src/core/options-mapping.ts index df716b922851..42629509a737 100644 --- a/packages/bundler-plugins/src/core/options-mapping.ts +++ b/packages/bundler-plugins/src/core/options-mapping.ts @@ -60,6 +60,7 @@ export type NormalizedOptions = { | { excludeDebugStatements?: boolean; excludeTracing?: boolean; + excludeChannelInjection?: boolean; excludeReplayCanvas?: boolean; excludeReplayShadowDom?: boolean; excludeReplayIframe?: boolean; diff --git a/packages/bundler-plugins/src/core/types.ts b/packages/bundler-plugins/src/core/types.ts index 7542b3c708b6..405687596ec1 100644 --- a/packages/bundler-plugins/src/core/types.ts +++ b/packages/bundler-plugins/src/core/types.ts @@ -303,6 +303,21 @@ export interface Options { */ excludeTracing?: boolean; + /** + * Exclude the Node SDK's runtime diagnostics-channel injection from the bundle. + * + * If set to `true`, the plugin will attempt to tree-shake (remove) code that installs the Node SDK's + * runtime module hooks (e.g. for Express instrumentation) at load time. Note that the success of this + * depends on tree-shaking being enabled in your build tooling. + * + * Only enable this when the diagnostics channels are injected at build time (via the bundler plugin) or + * when you otherwise do not rely on the runtime channel injection. This is equivalent to setting + * `enableRuntimeChannelInjection: false` in the SDK's `init` options. + * + * @default false + */ + excludeChannelInjection?: boolean; + /** * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Canvas recording functionality. * Note that the success of this depends on tree-shaking being enabled in your build tooling. @@ -526,6 +541,7 @@ export type IncludeEntry = { export interface SentrySDKBuildFlags extends Record { __SENTRY_DEBUG__?: boolean; __SENTRY_TRACING__?: boolean; + __SENTRY_CHANNEL_INJECTION__?: boolean; __RRWEB_EXCLUDE_CANVAS__?: boolean; __RRWEB_EXCLUDE_IFRAME__?: boolean; __RRWEB_EXCLUDE_SHADOW_DOM__?: boolean; diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 5323592842b3..081443fde8e4 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -47,6 +47,10 @@ import { NodeClient } from './client'; import { initOpenTelemetry } from './initOtel'; import { fastifyIntegration } from '../integrations/tracing/fastify'; +// Treeshakable guard to remove all code related to runtime diagnostics-channel injection. Set to +// `false` at build time by the Sentry bundler plugins' `bundleSizeOptimizations.excludeChannelInjection`. +declare const __SENTRY_CHANNEL_INJECTION__: boolean | undefined; + /** * Get the base default integrations shared by all Node SDK default-integration sets. */ @@ -166,10 +170,13 @@ function _init( }; // Install the channel-based (orchestrion diagnostics-channel) instrumentation hooks by default, - // independent of tracing — the channel integrations also capture errors, not just spans. Opt out - // with `enableRuntimeChannelInjection: false`. Install as early as possible, before the app imports - // its instrumented modules. - const useChannelInjection = options.enableRuntimeChannelInjection !== false; + // independent of tracing — the channel integrations also capture errors, not just spans. Opt out at + // runtime with `enableRuntimeChannelInjection: false`, or at build time via the bundler plugins' + // `bundleSizeOptimizations.excludeChannelInjection` (which tree-shakes this whole block away). + // Install as early as possible, before the app imports its instrumented modules. + const useChannelInjection = + (typeof __SENTRY_CHANNEL_INJECTION__ === 'undefined' || __SENTRY_CHANNEL_INJECTION__) && + options.enableRuntimeChannelInjection !== false; if (useChannelInjection) { registerDiagnosticsChannelInjection(); } diff --git a/packages/node/test/sdk/diagnosticsChannelInjection.test.ts b/packages/node/test/sdk/diagnosticsChannelInjection.test.ts index 90bb27e77f17..3d1c8e269af4 100644 --- a/packages/node/test/sdk/diagnosticsChannelInjection.test.ts +++ b/packages/node/test/sdk/diagnosticsChannelInjection.test.ts @@ -68,4 +68,18 @@ describe('diagnostics-channel injection', () => { expect(registerDiagnosticsChannelInjection).toHaveBeenCalledTimes(1); expect(detectOrchestrionSetup).toHaveBeenCalledTimes(1); }); + + it('does not register the injection hooks when the `__SENTRY_CHANNEL_INJECTION__` build flag is false', () => { + // Simulates the bundler plugins' `bundleSizeOptimizations.excludeChannelInjection` text-replacing the flag. + vi.stubGlobal('__SENTRY_CHANNEL_INJECTION__', false); + + try { + init({ dsn: PUBLIC_DSN, tracesSampleRate: 1, enableOpenTelemetrySetup: false }); + + expect(registerDiagnosticsChannelInjection).not.toHaveBeenCalled(); + expect(detectOrchestrionSetup).not.toHaveBeenCalled(); + } finally { + vi.unstubAllGlobals(); + } + }); });