diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index 1c87304185c..5a4292b6f36 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -38,6 +38,7 @@ import { createPropsDefaultThis } from './compat/props'
import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
import { shouldSkipAttr } from './compat/attrsFallthrough'
+import { createInternalObject } from './internalObject'
export type ComponentPropsOptions
=
| ComponentObjectPropsOptions
@@ -185,13 +186,6 @@ type NormalizedProp =
export type NormalizedProps = Record
export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
-/**
- * Used during vnode props normalization to check if the vnode props is the
- * attrs object of a component via `Object.getPrototypeOf`. This is more
- * performant than defining a non-enumerable property.
- */
-export const attrsProto = {}
-
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
@@ -199,7 +193,7 @@ export function initProps(
isSSR = false,
) {
const props: Data = {}
- const attrs: Data = Object.create(attrsProto)
+ const attrs: Data = createInternalObject()
instance.propsDefaults = Object.create(null)
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
index e0f051b3984..66a09e5fa1a 100644
--- a/packages/runtime-core/src/componentSlots.ts
+++ b/packages/runtime-core/src/componentSlots.ts
@@ -24,6 +24,7 @@ import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
import { toRaw } from '@vue/reactivity'
import { trigger } from '@vue/reactivity'
import { TriggerOpTypes } from '@vue/reactivity'
+import { createInternalObject } from './internalObject'
export type Slot = (
...args: IfAny
@@ -177,12 +178,12 @@ export const initSlots = (
} else {
normalizeObjectSlots(
children as RawSlots,
- (instance.slots = {}),
+ (instance.slots = createInternalObject()),
instance,
)
}
} else {
- instance.slots = {}
+ instance.slots = createInternalObject()
if (children) {
normalizeVNodeSlots(instance, children)
}
diff --git a/packages/runtime-core/src/internalObject.ts b/packages/runtime-core/src/internalObject.ts
new file mode 100644
index 00000000000..0c0c39bef6d
--- /dev/null
+++ b/packages/runtime-core/src/internalObject.ts
@@ -0,0 +1,12 @@
+/**
+ * Used during vnode props/slots normalization to check if the vnode props/slots
+ * are the internal attrs / slots object of a component via
+ * `Object.getPrototypeOf`. This is more performant than defining a
+ * non-enumerable property. (one of the optimizations done for ssr-benchmark)
+ */
+const internalObjectProto = Object.create(null)
+
+export const createInternalObject = () => Object.create(internalObjectProto)
+
+export const isInternalObject = (obj: object) =>
+ Object.getPrototypeOf(obj) === internalObjectProto
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index 28b60be78f2..a1a6a908d2a 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -55,7 +55,7 @@ import { convertLegacyVModelProps } from './compat/componentVModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import type { ComponentPublicInstance } from './componentPublicInstance'
-import { attrsProto } from './componentProps'
+import { isInternalObject } from './internalObject'
export const Fragment = Symbol.for('v-fgt') as any as {
__isFragment: true
@@ -617,9 +617,7 @@ function _createVNode(
export function guardReactiveProps(props: (Data & VNodeProps) | null) {
if (!props) return null
- return isProxy(props) || Object.getPrototypeOf(props) === attrsProto
- ? extend({}, props)
- : props
+ return isProxy(props) || isInternalObject(props) ? extend({}, props) : props
}
export function cloneVNode(
@@ -791,7 +789,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
} else {
type = ShapeFlags.SLOTS_CHILDREN
const slotFlag = (children as RawSlots)._
- if (!slotFlag) {
+ if (!slotFlag && !isInternalObject(children)) {
// if slots are not normalized, attach context instance
// (compiled / normalized slots already have context)
;(children as RawSlots)._ctx = currentRenderingInstance