Skip to content

ref(browser): Extract browser-specific normalize code out of core#21172

Merged
mydea merged 19 commits into
developfrom
fn/move-convertToPlainObject
May 28, 2026
Merged

ref(browser): Extract browser-specific normalize code out of core#21172
mydea merged 19 commits into
developfrom
fn/move-convertToPlainObject

Conversation

@mydea
Copy link
Copy Markdown
Member

@mydea mydea commented May 26, 2026

Summary

Today, @sentry/core carries browser-specific code that runs (or just ships) in every runtime: window / document / HTMLElement collapsing inside normalize(), Vue ViewModel detection, React SyntheticEvent detection, and htmlTreeAsString (DOM walk). This PR moves all of that into the packages that actually need it, leaving @sentry/core more runtime-agnostic (there are still a bunch of other things, but step by step...), and exposes a single new hook for SDKs to plug runtime-specific rendering into normalize().

Mechanism

packages/core/src/utils/normalize.ts now exposes:

export function setNormalizeStringifier(fn: ((value) => string | undefined) | undefined): void

normalize()'s internal stringifyValue consults the registered function before its runtime-agnostic fallbacks (NaN / function / symbol / bigint / [object ConstructorName]). Returning a string short-circuits; returning undefined falls through. Default state is no stringifier — server-only consumers never reach this code path.

What moved

Symbol New home Core export
htmlTreeAsString @sentry-internal/browser-utils @deprecated
isElement @sentry-internal/browser-utils @deprecated
isSyntheticEvent @sentry/react (internal) @deprecated
isVueViewModel @sentry/vue (internal) @deprecated
getVueInternalName @sentry/vue (internal) @deprecated

@sentry-internal/browser-utils also gains a new normalizeStringifyValue that handles window[Window], document[Document], and HTMLElement instances → [HTMLElement: <css-selector-path>] (via htmlTreeAsString). It explicitly does not handle Vue or React values — those are added by their respective SDKs.

SDK wiring

  • @sentry/browser init() calls setNormalizeStringifier(normalizeStringifyValue) (the browser-utils variant).
  • @sentry/vue init() runs after browserInit, then registers a wrapper that checks isVueViewModel and otherwise delegates to browser-utils normalizeStringifyValue.
  • @sentry/react init() does the same for isSyntheticEvent.

Other adjustments

  • safeJoin in core now uses stringifyValue from normalize instead of having special handling for vue, this should streamline things a bit. as a sideeffect, we also stringify certain things better now, e.g. html elements etc. get the nice tree format in browser

Tests

  • packages/core/test/lib/utils/normalize.test.ts — covers registry semantics: stub stringifier is consulted for every visited node, falls back to defaults when it returns undefined, behaves correctly with no stringifier registered.
  • packages/core/test/lib/utils/string.test.ts — new safeJoin() block covers primitive/non-primitive routing, default delimiter, non-array input, Error rendering, and interaction with setNormalizeStringifier.
  • packages/browser-utils/test/normalizeStringifyValue.test.ts — direct and integration tests for window / document / HTMLElement, plus confirmation that Vue/React values are not intercepted.
  • packages/vue/test/integration/normalize.test.ts — verifies Vue init's wrapper collapses Vue 2/3 ViewModels and VNodes while still delegating HTMLElement / document to the browser variant underneath.
  • packages/react/test/normalize.test.ts — same for React's SyntheticEvent wrap.

🤖 Generated with Claude Code

@mydea mydea self-assigned this May 26, 2026
Comment thread packages/browser-utils/src/domEventNormalizer.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.16 kB -0.43% -117 B 🔽
@sentry/browser - with treeshaking flags 25.61 kB -0.28% -71 B 🔽
@sentry/browser (incl. Tracing) 45.23 kB -0.06% -23 B 🔽
@sentry/browser (incl. Tracing + Span Streaming) 47.47 kB -0.06% -24 B 🔽
@sentry/browser (incl. Tracing, Profiling) 50.21 kB -0.04% -18 B 🔽
@sentry/browser (incl. Tracing, Replay) 84.81 kB -0.06% -44 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 74.38 kB +0.03% +20 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 89.52 kB -0.07% -54 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 102.14 kB -0.04% -38 B 🔽
@sentry/browser (incl. Feedback) 44.33 kB -0.29% -128 B 🔽
@sentry/browser (incl. sendFeedback) 31.98 kB -0.33% -104 B 🔽
@sentry/browser (incl. FeedbackAsync) 37.07 kB -0.37% -135 B 🔽
@sentry/browser (incl. Metrics) 28.24 kB -0.46% -128 B 🔽
@sentry/browser (incl. Logs) 28.48 kB -0.4% -114 B 🔽
@sentry/browser (incl. Metrics & Logs) 29.18 kB -0.37% -108 B 🔽
@sentry/react 28.99 kB -0.09% -24 B 🔽
@sentry/react (incl. Tracing) 47.5 kB +0.03% +11 B 🔺
@sentry/vue 32.2 kB +0.04% +10 B 🔺
@sentry/vue (incl. Tracing) 47.15 kB +0.08% +35 B 🔺
@sentry/svelte 27.18 kB -0.43% -117 B 🔽
CDN Bundle 29.55 kB -0.45% -133 B 🔽
CDN Bundle (incl. Tracing) 47.81 kB +0.07% +32 B 🔺
CDN Bundle (incl. Logs, Metrics) 31.05 kB -0.38% -118 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) 49.02 kB -0.03% -14 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) 70.31 kB -0.25% -174 B 🔽
CDN Bundle (incl. Tracing, Replay) 85.17 kB -0.13% -108 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.34 kB -0.12% -97 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 91.05 kB -0.11% -100 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.21 kB -0.12% -107 B 🔽
CDN Bundle - uncompressed 87.56 kB -0.16% -136 B 🔽
CDN Bundle (incl. Tracing) - uncompressed 144.03 kB -0.09% -120 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 92.04 kB -0.15% -138 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 147.78 kB -0.09% -120 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 216.77 kB -0.07% -138 B 🔽
CDN Bundle (incl. Tracing, Replay) - uncompressed 262.8 kB -0.05% -121 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 266.55 kB -0.05% -121 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 276.5 kB -0.05% -121 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 280.24 kB -0.05% -121 B 🔽
@sentry/nextjs (client) 50.01 kB +0.08% +36 B 🔺
@sentry/sveltekit (client) 45.66 kB -0.16% -72 B 🔽
@sentry/core/server 75.9 kB -0.68% -518 B 🔽
@sentry/core/browser 63.06 kB -0.17% -103 B 🔽
@sentry/node-core 61.69 kB -0.92% -568 B 🔽
@sentry/node 130.14 kB -0.46% -600 B 🔽
@sentry/node - without tracing 74.11 kB -0.8% -596 B 🔽
@sentry/aws-serverless 86.34 kB -0.66% -568 B 🔽
@sentry/cloudflare (withSentry) - minified 170.59 kB -1.06% -1.83 kB 🔽
@sentry/cloudflare (withSentry) 426.95 kB -0.87% -3.73 kB 🔽

View base workflow run

@mydea mydea force-pushed the fn/move-convertToPlainObject branch 2 times, most recently from 9729b8a to fbe2a21 Compare May 27, 2026 09:03
Comment thread packages/core/src/utils/string.ts Outdated
Comment thread packages/core/src/utils/object.ts
Comment thread packages/core/test/lib/utils/normalize.test.ts Outdated
@mydea mydea changed the title ref(browser): Extract browser-specific normalize code into registerable util ref(browser): Extract browser-specific normalize code out of core May 27, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02da5dd. Configure here.

Comment thread packages/core/src/asyncContext/types.ts Outdated
@mydea mydea force-pushed the fn/move-convertToPlainObject branch from 87b679e to 36cca78 Compare May 27, 2026 12:23
Copy link
Copy Markdown
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I think this makes things more targeted and cleaner.

One concern, but I think this is something we can address if it comes up: Us pulling out the Vue/React specific stuff into the respective framework SDKs is cleaner (fully agree!) but also means anyone using the default browser SDK (loader/CDN especially) on React/Vue apps won't get the specific functionality. Again, probably fine to fix if it becomes a real issue. The fix would be that we simply pull the added functionality into the browser-utils core version. We might need to eat the bundle size/minimal perf overhead increase at this point.

* Vue ViewModels and React SyntheticEvents are not handled here — the Vue and React
* SDKs wrap this function in their `init` and add their own checks on top.
*/
export function normalizeStringifyValue(value: Exclude<unknown, string | number | boolean | null>): string | undefined {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: I might be missing something obvious but can we hoist this up into @sentry/browser? I don't see it being imported by packages that don't depend on browser (as opposed to htmlTreeAsString)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, this makes sense 👍

@mydea
Copy link
Copy Markdown
Member Author

mydea commented May 27, 2026

One concern, but I think this is something we can address if it comes up: Us pulling out the Vue/React specific stuff into the respective framework SDKs is cleaner (fully agree!) but also means anyone using the default browser SDK (loader/CDN especially) on React/Vue apps won't get the specific functionality. Again, probably fine to fix if it becomes a real issue. The fix would be that we simply pull the added functionality into the browser-utils core version. We might need to eat the bundle size/minimal perf overhead increase at this point.

I agree, but I think in this case it is fine because mostly this is additive stuff (and a fix for a weird vue bug that may/may not be around still, sadly we don't really have integration/e2e tests where we know this is covered...), so it feels fine to say you only get this if using the proper SDK. E.g. for react, this will just be serialized a bit differently which is also not the end of the world I'd say :)

Relocates `normalizeStringifyValue` (and its test) out of
`@sentry-internal/browser-utils` into `@sentry/browser`, exports it from
the browser package's public surface, and switches `@sentry/vue` and
`@sentry/react` to import from `@sentry/browser` instead.

Both framework packages no longer need a direct dependency on
`@sentry-internal/browser-utils`, so that entry is dropped from their
`package.json` files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mydea mydea marked this pull request as ready for review May 27, 2026 14:59
@mydea mydea requested review from a team as code owners May 27, 2026 14:59
@mydea mydea requested review from logaretm, nicohrubec and s1gr1d and removed request for a team May 27, 2026 14:59
@mydea mydea merged commit 02cd482 into develop May 28, 2026
265 of 266 checks passed
@mydea mydea deleted the fn/move-convertToPlainObject branch May 28, 2026 07:38
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.

2 participants