From 21a51b10a166758d782c42362dd8d10e55fc05ea Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 17 Mar 2026 17:40:26 +0100 Subject: [PATCH] refactor(core): Inline Vue ViewModel checks in normalize and safeJoin The isVueViewModel() and getVueInternalName() functions from is.ts and stacktrace.ts were only called from two sites: normalize.ts (stringifyValue) and string.ts (safeJoin). Inlining the checks directly at these call sites allows tree-shaking to eliminate the standalone function definitions from bundles that pull in normalize/string but not is.ts for other reasons. Uses the `in` operator instead of `as any` casts to satisfy the linter's no-explicit-any / no-unsafe-member-access rules. The Vue check prevents infinite console warning loops when stringifying Vue 2/3 ViewModel instances or VNodes (see #8981). Saves ~15 bytes gzipped on the base browser bundle. Co-Authored-By: Claude claude@anthropic.com --- packages/core/src/utils/normalize.ts | 12 ++++++++---- packages/core/src/utils/string.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/core/src/utils/normalize.ts b/packages/core/src/utils/normalize.ts index 1c25d937cfe4..cf55a83dd7df 100644 --- a/packages/core/src/utils/normalize.ts +++ b/packages/core/src/utils/normalize.ts @@ -1,7 +1,7 @@ import type { Primitive } from '../types-hoist/misc'; -import { isSyntheticEvent, isVueViewModel } from './is'; +import { isSyntheticEvent } from './is'; import { convertToPlainObject } from './object'; -import { getFunctionName, getVueInternalName } from './stacktrace'; +import { getFunctionName } from './stacktrace'; type Prototype = { constructor?: (...args: unknown[]) => unknown }; // This is a hack to placate TS, relying on the fact that technically, arrays are objects with integer keys. Normally we @@ -216,8 +216,12 @@ function stringifyValue( return '[Document]'; } - if (isVueViewModel(value)) { - return getVueInternalName(value); + if ( + typeof value === 'object' && + value !== null && + ('__isVue' in value || '_isVue' in value || '__v_isVNode' in value) + ) { + return '__v_isVNode' in value && value.__v_isVNode ? '[VueVNode]' : '[VueViewModel]'; } // React's SyntheticEvent thingy diff --git a/packages/core/src/utils/string.ts b/packages/core/src/utils/string.ts index fe28ef24c46a..82a1417e2061 100644 --- a/packages/core/src/utils/string.ts +++ b/packages/core/src/utils/string.ts @@ -1,5 +1,4 @@ -import { isRegExp, isString, isVueViewModel } from './is'; -import { getVueInternalName } from './stacktrace'; +import { isRegExp, isString } from './is'; export { escapeStringForRegex } from '../vendor/escapeStringForRegex'; @@ -76,13 +75,14 @@ export function safeJoin(input: unknown[], delimiter?: string): string { for (let i = 0; i < input.length; i++) { const value = input[i]; try { - // This is a hack to fix a Vue3-specific bug that causes an infinite loop of - // console warnings. This happens when a Vue template is rendered with - // an undeclared variable, which we try to stringify, ultimately causing - // Vue to issue another warning which repeats indefinitely. + // Vue3 ViewModels and VNodes can cause infinite console warning loops when stringified // see: https://github.com/getsentry/sentry-javascript/pull/8981 - if (isVueViewModel(value)) { - output.push(getVueInternalName(value)); + if ( + typeof value === 'object' && + value !== null && + ('__isVue' in value || '_isVue' in value || '__v_isVNode' in value) + ) { + output.push('__v_isVNode' in value && value.__v_isVNode ? '[VueVNode]' : '[VueViewModel]'); } else { output.push(String(value)); }