Skip to content

fix(server-utils): Bundle orchestrion runtime chain so it survives downstream tree-shaking - #23668

Closed
mydea wants to merge 3 commits into
developfrom
fix/orchestrion-treeshake
Closed

fix(server-utils): Bundle orchestrion runtime chain so it survives downstream tree-shaking#23668
mydea wants to merge 3 commits into
developfrom
fix/orchestrion-treeshake

Conversation

@mydea

@mydea mydea commented Aug 27, 2026

Copy link
Copy Markdown
Member

When a downstream bundler (Next.js server, serverless, nitro/vite — rollup and rolldown alike) re-bundles @sentry/node, the vendored orchestrion transformer chain pulled in by orchestrion/register (@apm-js-collab/code-transformer → meriyah, esquery, astring, source-map) gets partially tree-shaken away. meriyah/astring/source-map are emptied while esquery survives, so at runtime parse/generate/the SourceMap constructors are undefined and any instrumented module throws TypeError: parse is not a function when loaded.

Root cause

Under preserveModules, @rollup/plugin-commonjs emits each named-export CJS dep as an empty proxy object (var meriyah = {}) that a separate module populates via cross-module property writes (meriyah.parse = parse), reachable only through a bare side-effect import. Downstream tree-shakers treat those writes as dead code and drop them, leaving the proxy empty. esquery escaped this only because it ships module.exports = {…} — a whole default-export object with nothing to tree-shake off it — which is why the chain was half-shaken rather than fully removed.

The bug lives in the vendored build's output shape, not in package metadata: a sideEffects allowlist does not help, because Rollup still applies statement-level dead-code elimination to the cross-module property writes even in a module it considers to have side effects. I verified this both ways before landing on the output-shape fix.

Fix

Build the orchestrion/register and orchestrion/hook runtime entrypoints without preserveModules, so the whole chain lands in one self-contained shared chunk where each dep's proxy object, its population, and its consumer are co-located in a single module. Rollup never separates a property write from a read within one module, so the chain survives downstream tree-shaking even under this package's sideEffects: false. The rest of the package keeps preserveModules for fine-grained consumer tree-shaking.

Only these two entrypoints needed changing — they are the only ones that ship the transformer chain into the user's app. The build-time bundler plugins (orchestrion/vite, etc.) also carry the chain but run in Node at build time where nothing tree-shakes them, so they are left on preserveModules. The trade-off is ~1MB of build-time-only duplication in the (already sourcemap-heavy) published tarball; it does not affect consumer app bundle size.

The shared rollup-plugin-license instance now runs across both rollup configs. It accumulates scanned deps into one Map, so whichever build writes last emits the complete THIRD-PARTY-LICENSES.txt union even though the two configs bundle different subsets.

The added regression test re-bundles the built register entry the way a downstream bundler does and asserts the vendored deps stay populated; it fails on develop and passes with the fix. The CJS runtime chunk was verified to stay genuine CJS (node: builtins only, no require(esm)), preserving the original reasons for vendoring.

Fixes #23664

🤖 Generated with Claude Code

mydea and others added 2 commits August 27, 2026 10:09
Add a regression test that re-bundles the built `orchestrion/register` runtime
entry the way a downstream bundler does (honouring the package `sideEffects`)
and asserts the vendored CJS deps (meriyah, astring, source-map) stay populated.

Under `preserveModules`, `@rollup/plugin-commonjs` emits those deps as empty
proxy objects populated by cross-module property writes reachable only through
bare side-effect imports; downstream tree-shaking drops those writes, so
`parse`/`generate`/the SourceMap constructors become `undefined` and every
instrumented module crashes when loaded.

The test fails on develop and documents the expected behaviour for the fix.

Ref #23664

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wnstream tree-shaking

The `orchestrion/register` and `orchestrion/hook` runtime entrypoints pull in the
vendored transformer chain (`@apm-js-collab/code-transformer` → meriyah, esquery,
astring, source-map). Under `preserveModules`, `@rollup/plugin-commonjs` emits
each named-export CJS dep (meriyah, astring, source-map) as an empty proxy object
(`var meriyah = {}`) that a separate module populates via cross-module property
writes (`meriyah.parse = parse`), reachable only through a bare side-effect import.

When a downstream bundler (Next.js server, serverless, nitro/vite — rollup and
rolldown alike) re-bundles `@sentry/node`, its tree-shaker drops those "unused"
property writes, leaving the proxy empty. At runtime `parse`/`generate`/the
SourceMap constructors are then `undefined`, so any instrumented module throws
`TypeError: parse is not a function` when loaded. esquery escaped this only
because it ships `module.exports = {…}` (a whole default-export object with
nothing to tree-shake off it), which is why the chain was half-shaken rather than
fully removed.

Build those two entrypoints without `preserveModules` so the whole chain lands in
one self-contained shared chunk where each dep's proxy object, its population, and
its consumer are co-located in a single module. Rollup never separates a property
write from a read within one module, so the chain survives downstream tree-shaking
even under this package's `sideEffects: false`. The rest of the package keeps
`preserveModules` for fine-grained consumer tree-shaking.

Root cause: the bug is in the vendored build's output shape, not package metadata
— a `sideEffects` allowlist does not help, because Rollup still applies
statement-level dead-code elimination to the cross-module property writes even in
a module it considers to have side effects. Co-locating the chain is the only
reliable fix.

The shared `rollup-plugin-license` instance now runs across both configs; it
accumulates scanned deps into one Map, so the last build writes the complete
`THIRD-PARTY-LICENSES.txt` union even though the two configs bundle different
subsets.

Fixes #23664

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.57 kB - -
@sentry/browser - with treeshaking flags 26.92 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 26.82 kB - -
@sentry/browser (incl. Tracing) 48.61 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.63 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.54 kB - -
@sentry/browser (incl. Tracing, Replay) 88.1 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.48 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 92.8 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 105.48 kB - -
@sentry/browser (incl. Feedback) 45.79 kB - -
@sentry/browser (incl. sendFeedback) 33.35 kB - -
@sentry/browser (incl. FeedbackAsync) 38.46 kB - -
@sentry/browser (incl. Metrics) 29.51 kB - -
@sentry/browser (incl. Logs) 29.8 kB - -
@sentry/browser (incl. Metrics & Logs) 30.43 kB - -
@sentry/react 30.31 kB - -
@sentry/react (incl. Tracing) 50.82 kB - -
@sentry/vue 35.69 kB - -
@sentry/vue (incl. Tracing) 50.86 kB - -
@sentry/svelte 28.59 kB - -
CDN Bundle 30.36 kB - -
CDN Bundle (incl. Tracing) 49.09 kB - -
CDN Bundle (incl. Logs, Metrics) 32.56 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 50.98 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73 kB - -
CDN Bundle (incl. Tracing, Replay) 86.6 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.5 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 92.38 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94.3 kB - -
CDN Bundle - uncompressed 89.97 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.77 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 96.26 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 152.46 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 225.42 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 266.27 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 271.94 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.96 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 285.62 kB - -
@sentry/nextjs (client) 53.39 kB - -
@sentry/sveltekit (client) 49.05 kB - -
@sentry/core/server 65.3 kB - -
@sentry/core/browser 52.35 kB - -
⛔️ @sentry/node (max: 123 kB) 183.94 kB +50.37% +61.61 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 85.14 kB -0.1% -79 B 🔽
⛔️ @sentry/node - without tracing (max: 92 kB) 149.07 kB +70.09% +61.43 kB 🔺
@sentry/node - without channel injection 102.04 kB +0.02% +17 B 🔺
⛔️ @sentry/aws-serverless (max: 99 kB) 157.22 kB +64.12% +61.42 kB 🔺
@sentry/cloudflare (withSentry) - minified 199.57 kB - -
@sentry/cloudflare (withSentry) 495.71 kB - -

View base workflow run

…ith a long timeout

The nx build cache is Node-version-scoped, so on Node versions other than the one
the CI build job ran on, `build/` is absent and the test's on-demand
`yarn build:transpile` plus the rollup re-bundle exceeded the default 5s test
timeout (the test timed out on Node 22/24/26 while passing on Node 20).

Move both the build and the re-bundle into `beforeAll` under a single 180s
timeout and reduce the test body to fast string assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea

mydea commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

Superseded by #23675, which pivots from bundling-the-transformer (which added ~61 kB and only partly fixed the issue) to detecting + warning when the runtime hook is bundled and keeping it external. Closing in favour of that approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

orchestrion deps are partially tree-shaken

1 participant