ref(browser): Extract browser-specific `normalize` code out of core (#21172)
## 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: ```ts 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](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F
Francesco Gringl-Novy committed
02cd482fa3c600426619ef9f0139e11610a1d154
Parent: 5bd7ea2
Committed by GitHub <noreply@github.com>
on 5/28/2026, 7:38:29 AM