Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/electron (which depends on @sentry/node)
SDK Version
@sentry/electron 7.16.0, @sentry/node 10.67.0, @sentry/server-utils 10.67.0 (also verified against 10.71.0, see below)
Framework Version
Electron 43.3.0, Node 24.15.0, packaged with electron-builder
Link to Sentry event
N/A — this is a packaging/bundle-size issue, not a runtime error.
Reproduction Example/SDK Setup
Any app that depends on @sentry/node (directly or via @sentry/electron) and is packaged by a tool that walks the production dependencies tree rather than the call graph — electron-builder, npm install --omit=dev in a Docker image, a serverless bundle, etc.
import * as Sentry from '@sentry/electron/main';
Sentry.init({ dsn: '...' });
Steps to Reproduce
- Depend on
@sentry/node (or @sentry/electron, or anything else that pulls in @sentry/server-utils).
- Install production dependencies only, or package the app with electron-builder.
- Inspect the resulting
node_modules tree.
Expected Result
The production dependency tree contains runtime code only. Build-time bundler plugins and the JavaScript parser / AST toolchain they need are not part of it.
Actual Result
@sentry/server-utils declares its bundler plugins as production dependencies:
// @sentry/server-utils@10.67.0
"dependencies": {
"@apm-js-collab/code-transformer-bundler-plugins": "^0.7.1",
"@apm-js-collab/tracing-hooks": "^0.13.0",
"@sentry/conventions": "^0.16.0",
"@sentry/core": "10.67.0"
},
"devDependencies": { "@types/node": "...", "vite": "..." }
There is no peerDependenciesMeta.optional and no optionalDependencies, so every consumer installs the full transitive closure of those two packages:
@apm-js-collab/code-transformer-bundler-plugins -> @apm-js-collab/code-transformer, es-module-lexer,
magic-string, module-details-from-path
@apm-js-collab/code-transformer -> meriyah, esquery, astring, estraverse,
source-map, semifies, @types/estree
That is a complete JavaScript parser (meriyah), an AST query engine (esquery), a code generator (astring) and a source-map toolchain, shipped to production consumers. Installed sizes in our tree:
| package |
installed |
meriyah |
1472 KB |
esquery |
1104 KB |
source-map |
824 KB |
@apm-js-collab/code-transformer-bundler-plugins |
668 KB |
magic-string |
476 KB |
astring |
292 KB |
es-module-lexer |
192 KB |
@jridgewell/sourcemap-codec |
164 KB |
@apm-js-collab/code-transformer |
104 KB |
estraverse |
52 KB |
@apm-js-collab/tracing-hooks |
48 KB |
semifies |
24 KB |
These are build-time consumers only. Every reference to the parser comes from the bundler plugin entrypoints:
@apm-js-collab/code-transformer/lib/{transformer,transforms}.js requires meriyah, esquery, astring, source-map
build/cjs/orchestrion/bundler/{vite,rollup,webpack,esbuild}.js are the only files in @sentry/server-utils that require @apm-js-collab/code-transformer-bundler-plugins
build/cjs/index.js (the main entry) never reaches any of them
We consume @sentry/electron/main, @sentry/electron/renderer and @sentry/node, and never import an orchestrion/* subpath or call experimentalUseDiagnosticsChannelInjection. Confirmed empirically: patching Module._resolveFilename to make all 11 packages unresolvable, then loading and initializing the SDK (Sentry.init + captureException) in both plain Node and a real Electron main process, produces zero requests for any of them. Removing them from a packaged Electron app entirely (so they are physically absent from disk) and then requiring @sentry/node, @sentry/server-utils and @sentry/node-core from inside that tree also works: init and capture succeed.
In our case this is 2.26 MiB of a packaged Electron app.asar (measured: 48,171,148 → 45,802,904 bytes) that no code path can reach.
Why consumers cannot cleanly fix this themselves
Excluding the packages at packaging time works today but is fragile, because @sentry/node's main entry eagerly loads the door into this subsystem:
// @sentry/node/build/cjs/index.js
require('./sdk/experimentalUseDiagnosticsChannelInjection.js');
// that module, at top level:
const orchestrion = require('@sentry/server-utils/orchestrion');
const register = require('@sentry/server-utils/orchestrion/register');
The parser stays unreachable only because orchestrion/runtime/register.js resolves tracing-hooks lazily, inside registerDiagnosticsChannelInjection(), which runs only when a consumer opts in. So a consumer-side exclusion is one dynamic require away from becoming a production startup crash — and one that no typecheck, test suite or successful build would catch, since it only manifests in the packaged artifact.
That risk is not hypothetical for a subsystem still marked experimental: 10.71.0 added meriyah as a direct dependency of @sentry/server-utils for a new orchestrion/bundler/subscribeInjection.js. Still unreachable from the main entry (I checked), but it shows the tree moves between patch releases, so consumers would have to re-verify reachability on every bump.
Suggested fix
Move @apm-js-collab/code-transformer-bundler-plugins (and meriyah in 10.71.0+) out of dependencies — either to devDependencies if the bundler entrypoints are expected to be used from a dev-time context that already has them, or to peerDependenciesMeta.optional so consumers who do use a bundler plugin install it explicitly while runtime-only consumers pay nothing.
@apm-js-collab/tracing-hooks is the more interesting one: it is genuinely runtime, but only for consumers who enable the diagnostics-channel injection, and it drags in the same parser through @apm-js-collab/code-transformer. Making it optional too (the register.js code path already resolves it lazily and could fail gracefully) would let runtime-only consumers avoid the whole AST toolchain.
Related
#22794 looks like the same root cause with a worse symptom: there a build-time bundler plugin is reachable from a runtime entry, and its inlined WASM makes WebAssembly.compile() run at module evaluation, crashing on every Cloudflare Workers cold start. Our report is the benign variant of the same shape — not reachable, just shipped. Fixing the dependency classification would address both classes.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/electron (which depends on @sentry/node)
SDK Version
@sentry/electron 7.16.0, @sentry/node 10.67.0, @sentry/server-utils 10.67.0 (also verified against 10.71.0, see below)
Framework Version
Electron 43.3.0, Node 24.15.0, packaged with electron-builder
Link to Sentry event
N/A — this is a packaging/bundle-size issue, not a runtime error.
Reproduction Example/SDK Setup
Any app that depends on
@sentry/node(directly or via@sentry/electron) and is packaged by a tool that walks the productiondependenciestree rather than the call graph — electron-builder,npm install --omit=devin a Docker image, a serverless bundle, etc.Steps to Reproduce
@sentry/node(or@sentry/electron, or anything else that pulls in@sentry/server-utils).node_modulestree.Expected Result
The production dependency tree contains runtime code only. Build-time bundler plugins and the JavaScript parser / AST toolchain they need are not part of it.
Actual Result
@sentry/server-utilsdeclares its bundler plugins as productiondependencies:There is no
peerDependenciesMeta.optionaland nooptionalDependencies, so every consumer installs the full transitive closure of those two packages:That is a complete JavaScript parser (
meriyah), an AST query engine (esquery), a code generator (astring) and a source-map toolchain, shipped to production consumers. Installed sizes in our tree:meriyahesquerysource-map@apm-js-collab/code-transformer-bundler-pluginsmagic-stringastringes-module-lexer@jridgewell/sourcemap-codec@apm-js-collab/code-transformerestraverse@apm-js-collab/tracing-hookssemifiesThese are build-time consumers only. Every reference to the parser comes from the bundler plugin entrypoints:
@apm-js-collab/code-transformer/lib/{transformer,transforms}.jsrequiresmeriyah,esquery,astring,source-mapbuild/cjs/orchestrion/bundler/{vite,rollup,webpack,esbuild}.jsare the only files in@sentry/server-utilsthat require@apm-js-collab/code-transformer-bundler-pluginsbuild/cjs/index.js(the main entry) never reaches any of themWe consume
@sentry/electron/main,@sentry/electron/rendererand@sentry/node, and never import anorchestrion/*subpath or callexperimentalUseDiagnosticsChannelInjection. Confirmed empirically: patchingModule._resolveFilenameto make all 11 packages unresolvable, then loading and initializing the SDK (Sentry.init+captureException) in both plain Node and a real Electron main process, produces zero requests for any of them. Removing them from a packaged Electron app entirely (so they are physically absent from disk) and then requiring@sentry/node,@sentry/server-utilsand@sentry/node-corefrom inside that tree also works: init and capture succeed.In our case this is 2.26 MiB of a packaged Electron
app.asar(measured: 48,171,148 → 45,802,904 bytes) that no code path can reach.Why consumers cannot cleanly fix this themselves
Excluding the packages at packaging time works today but is fragile, because
@sentry/node's main entry eagerly loads the door into this subsystem:The parser stays unreachable only because
orchestrion/runtime/register.jsresolvestracing-hookslazily, insideregisterDiagnosticsChannelInjection(), which runs only when a consumer opts in. So a consumer-side exclusion is one dynamicrequireaway from becoming a production startup crash — and one that no typecheck, test suite or successful build would catch, since it only manifests in the packaged artifact.That risk is not hypothetical for a subsystem still marked experimental: 10.71.0 added
meriyahas a direct dependency of@sentry/server-utilsfor a neworchestrion/bundler/subscribeInjection.js. Still unreachable from the main entry (I checked), but it shows the tree moves between patch releases, so consumers would have to re-verify reachability on every bump.Suggested fix
Move
@apm-js-collab/code-transformer-bundler-plugins(andmeriyahin 10.71.0+) out ofdependencies— either todevDependenciesif the bundler entrypoints are expected to be used from a dev-time context that already has them, or topeerDependenciesMeta.optionalso consumers who do use a bundler plugin install it explicitly while runtime-only consumers pay nothing.@apm-js-collab/tracing-hooksis the more interesting one: it is genuinely runtime, but only for consumers who enable the diagnostics-channel injection, and it drags in the same parser through@apm-js-collab/code-transformer. Making it optional too (theregister.jscode path already resolves it lazily and could fail gracefully) would let runtime-only consumers avoid the whole AST toolchain.Related
#22794 looks like the same root cause with a worse symptom: there a build-time bundler plugin is reachable from a runtime entry, and its inlined WASM makes
WebAssembly.compile()run at module evaluation, crashing on every Cloudflare Workers cold start. Our report is the benign variant of the same shape — not reachable, just shipped. Fixing the dependency classification would address both classes.