]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(runtime-vapor): use dedicated currentSlotOwner instead of changing currentIn...
authordaiwei <daiwei521@126.com>
Thu, 4 Dec 2025 04:03:09 +0000 (12:03 +0800)
committeredison <daiwei521@126.com>
Thu, 4 Dec 2025 07:30:34 +0000 (15:30 +0800)
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/components/Teleport.ts
packages/runtime-vapor/src/fragment.ts
packages/runtime-vapor/src/vdomInterop.ts

index 9371c1a1abdac3a73341db70253b2209f25d1127..c1098e3879c4acb6fcd9a34c01e996b2487cf4ce 100644 (file)
@@ -77,10 +77,10 @@ import {
   type RawSlots,
   type StaticSlots,
   type VaporSlot,
+  currentSlotOwner,
   dynamicSlotsProxyHandlers,
-  getParentInstance,
   getSlot,
-  setCurrentSlotConsumer,
+  setCurrentSlotOwner,
 } from './componentSlots'
 import { hmrReload, hmrRerender } from './hmr'
 import {
@@ -202,24 +202,22 @@ export function createComponent(
     resetInsertionState()
   }
 
-  const parentInstance = getParentInstance()
-
   let prevSuspense: SuspenseBoundary | null = null
-  if (__FEATURE_SUSPENSE__ && parentInstance && parentInstance.suspense) {
-    prevSuspense = setParentSuspense(parentInstance.suspense)
+  if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) {
+    prevSuspense = setParentSuspense(currentInstance.suspense)
   }
 
   if (
     (isSingleRoot ||
       // transition has attrs fallthrough
-      (parentInstance && isVaporTransition(parentInstance!.type))) &&
+      (currentInstance && isVaporTransition(currentInstance!.type))) &&
     component.inheritAttrs !== false &&
-    isVaporComponent(parentInstance) &&
-    parentInstance.hasFallthrough
+    isVaporComponent(currentInstance) &&
+    currentInstance.hasFallthrough
   ) {
     // check if we are the single root of the parent
     // if yes, inject parent attrs as dynamic props source
-    const attrs = parentInstance.attrs
+    const attrs = currentInstance.attrs
     if (rawProps && rawProps !== EMPTY_OBJ) {
       ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
         () => attrs,
@@ -230,8 +228,12 @@ export function createComponent(
   }
 
   // keep-alive
-  if (parentInstance && parentInstance.vapor && isKeepAlive(parentInstance)) {
-    const cached = (parentInstance as KeepAliveInstance).getCachedComponent(
+  if (
+    currentInstance &&
+    currentInstance.vapor &&
+    isKeepAlive(currentInstance)
+  ) {
+    const cached = (currentInstance as KeepAliveInstance).getCachedComponent(
       component,
     )
     // @ts-expect-error
@@ -240,14 +242,12 @@ export function createComponent(
 
   // vdom interop enabled and component is not an explicit vapor component
   if (appContext.vapor && !component.__vapor) {
-    const prevSlotConsumer = setCurrentSlotConsumer(null)
     const frag = appContext.vapor.vdomMount(
       component as any,
-      parentInstance as any,
+      currentInstance as any,
       rawProps,
       rawSlots,
     )
-    setCurrentSlotConsumer(prevSlotConsumer)
     if (!isHydrating) {
       if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
     } else {
@@ -280,11 +280,10 @@ export function createComponent(
     rawSlots as RawSlots,
     appContext,
     once,
-    parentInstance,
   )
 
-  // set currentSlotConsumer to null to avoid affecting the child components
-  const prevSlotConsumer = setCurrentSlotConsumer(null)
+  // reset currentSlotOwner to null to avoid affecting the child components
+  const prevSlotOwner = setCurrentSlotOwner(null)
 
   // HMR
   if (__DEV__) {
@@ -347,12 +346,12 @@ export function createComponent(
     endMeasure(instance, 'init')
   }
 
-  if (__FEATURE_SUSPENSE__ && parentInstance && parentInstance.suspense) {
+  if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) {
     setParentSuspense(prevSuspense)
   }
 
-  // restore currentSlotConsumer to previous value after setupFn is called
-  setCurrentSlotConsumer(prevSlotConsumer)
+  // restore currentSlotOwner to previous value after setupFn is called
+  setCurrentSlotOwner(prevSlotOwner)
   onScopeDispose(() => unmountComponent(instance), true)
 
   if (_insertionParent || isHydrating) {
@@ -594,19 +593,19 @@ export class VaporComponentInstance implements GenericComponentInstance {
     rawSlots?: RawSlots | null,
     appContext?: GenericAppContext,
     once?: boolean,
-    parent: GenericComponentInstance | null = currentInstance,
   ) {
     this.vapor = true
     this.uid = nextUid()
     this.type = comp
-    this.parent = parent
-    this.root = parent ? parent.root : this
+    this.parent = currentInstance
 
-    if (parent) {
-      this.appContext = parent.appContext
-      this.provides = parent.provides
-      this.ids = parent.ids
+    if (currentInstance) {
+      this.root = currentInstance.root
+      this.appContext = currentInstance.appContext
+      this.provides = currentInstance.provides
+      this.ids = currentInstance.ids
     } else {
+      this.root = this
       this.appContext = appContext || emptyContext
       this.provides = Object.create(this.appContext.provides)
       this.ids = ['', 0, 0]
@@ -655,7 +654,10 @@ export class VaporComponentInstance implements GenericComponentInstance {
         : rawSlots
       : EMPTY_OBJ
 
-    this.scopeId = currentInstance && currentInstance.type.__scopeId
+    // Use currentSlotOwner for scopeId inheritance when inside a slot
+    // This ensures components created in slots inherit the slot owner's scopeId
+    const scopeOwner = currentSlotOwner || currentInstance
+    this.scopeId = scopeOwner && scopeOwner.type.__scopeId
 
     // apply custom element special handling
     if (comp.ce) {
@@ -745,7 +747,9 @@ export function createPlainElement(
   ;(el as any).$root = isSingleRoot
 
   if (!isHydrating) {
-    const scopeId = currentInstance!.type.__scopeId
+    // Use currentSlotOwner for scopeId when inside a slot
+    const scopeOwner = currentSlotOwner || currentInstance
+    const scopeId = scopeOwner!.type.__scopeId
     if (scopeId) setScopeId(el, [scopeId])
   }
 
index b450e077a1409236fc43ae058d8c270c8dfda42d..273f2569dddf85a0aab5edf13f2158b4c8638852 100644 (file)
@@ -6,7 +6,6 @@ import {
   currentInstance,
   isAsyncWrapper,
   isRef,
-  setCurrentInstance,
 } from '@vue/runtime-dom'
 import type { LooseRawProps, VaporComponentInstance } from './component'
 import { renderEffect } from './renderEffect'
@@ -124,42 +123,47 @@ export function getSlot(
   }
 }
 
-export let currentSlotConsumer: GenericComponentInstance | null = null
+/**
+ * Tracks the slot owner (the component that defines the slot content).
+ * This is used for:
+ * 1. Getting the correct rawSlots in forwarded slots (via createSlot)
+ * 2. Inheriting the slot owner's scopeId
+ */
+export let currentSlotOwner: VaporComponentInstance | null = null
 
-export function setCurrentSlotConsumer(
-  consumer: GenericComponentInstance | null,
-): GenericComponentInstance | null {
+export function setCurrentSlotOwner(
+  owner: VaporComponentInstance | null,
+): VaporComponentInstance | null {
   try {
-    return currentSlotConsumer
+    return currentSlotOwner
   } finally {
-    currentSlotConsumer = consumer
+    currentSlotOwner = owner
   }
 }
 
 /**
- * use currentSlotConsumer as parent, the currentSlotConsumer will be reset to null
- * before setupFn call to avoid affecting children and restore to previous value
- * after setupFn is called
+ * Get the effective slot instance for accessing rawSlots and scopeId.
+ * Prefers currentSlotOwner (if inside a slot), falls back to currentInstance.
  */
-export function getParentInstance(): GenericComponentInstance | null {
-  return currentSlotConsumer || currentInstance
+export function getSlotInstance(): VaporComponentInstance {
+  return (currentSlotOwner || currentInstance) as VaporComponentInstance
 }
 
 /**
- * Wrap a slot function to memoize currentInstance
- * 1. ensure correct currentInstance in forwarded slots
- * 2. elements created in the slot inherit the slot owner's scopeId
+ * Wrap a slot function to track the slot owner.
+ *
+ * This ensures:
+ * 1. createSlot gets rawSlots from the correct component (slot owner)
+ * 2. Elements inherit the slot owner's scopeId
  */
 export function withVaporCtx(fn: Function): BlockFn {
-  const owner = currentInstance
+  const owner = currentInstance as VaporComponentInstance
   return (...args: any[]) => {
-    const prev = setCurrentInstance(owner)
-    const prevConsumer = setCurrentSlotConsumer(prev[0])
+    const prevOwner = setCurrentSlotOwner(owner)
     try {
       return fn(...args)
     } finally {
-      setCurrentInstance(...prev)
-      setCurrentSlotConsumer(prevConsumer)
+      setCurrentSlotOwner(prevOwner)
     }
   }
 }
@@ -176,7 +180,8 @@ export function createSlot(
   const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
 
-  const instance = currentInstance as VaporComponentInstance
+  // Use slot owner if inside a slot (forwarded slots), otherwise use currentInstance
+  const instance = getSlotInstance()
   const rawSlots = instance.rawSlots
   const slotProps = rawProps
     ? new Proxy(rawProps, rawPropsProxyHandlers)
index f83da4f8bdacce743e64c177c35b4f1e19a7a112..9379cdabdbeb9d923df95d2282edcdd7fa8fc575 100644 (file)
@@ -3,6 +3,7 @@ import {
   MismatchTypes,
   type TeleportProps,
   type TeleportTargetElement,
+  currentInstance,
   isMismatchAllowed,
   isTeleportDeferred,
   isTeleportDisabled,
@@ -31,7 +32,6 @@ import {
   setCurrentHydrationNode,
 } from '../dom/hydration'
 import { applyTransitionHooks } from './Transition'
-import { getParentInstance } from '../componentSlots'
 
 export const VaporTeleportImpl = {
   name: 'VaporTeleport',
@@ -62,7 +62,7 @@ export class TeleportFragment extends VaporFragment {
     super([])
     this.rawProps = props
     this.rawSlots = slots
-    this.parentComponent = getParentInstance()
+    this.parentComponent = currentInstance
     this.anchor = isHydrating
       ? undefined
       : __DEV__
index ed0b64e4246ec4f4212a8ecde5195af9fe8926bc..2c3f5554a31d906327eb896bdb1b73581a2043a0 100644 (file)
@@ -14,6 +14,7 @@ import {
   type GenericComponentInstance,
   type TransitionHooks,
   type VNode,
+  currentInstance,
   queuePostFlushCb,
   setCurrentInstance,
   warnExtraneousAttributes,
@@ -31,7 +32,6 @@ import {
   locateFragmentEndAnchor,
   locateHydrationNode,
 } from './dom/hydration'
-import { getParentInstance } from './componentSlots'
 import { isArray } from '@vue/shared'
 import { renderEffect } from './renderEffect'
 
@@ -99,7 +99,7 @@ export class DynamicFragment extends VaporFragment {
 
   constructor(anchorLabel?: string) {
     super([])
-    this.parentComponent = getParentInstance()
+    this.parentComponent = currentInstance
     if (isHydrating) {
       this.anchorLabel = anchorLabel
       locateHydrationNode()
index 60b5aad1f44c59fa98563e6b4a6fff863b04f131..64ece00d88ac4663fed603cf199080b68317e1ea 100644 (file)
@@ -60,7 +60,7 @@ import {
 } from '@vue/shared'
 import { type RawProps, rawPropsProxyHandlers } from './componentProps'
 import type { RawSlots, VaporSlot } from './componentSlots'
-import { currentSlotScopeIds } from './componentSlots'
+import { currentSlotScopeIds, getSlotInstance } from './componentSlots'
 import { renderEffect } from './renderEffect'
 import { _next, createTextNode } from './dom/node'
 import { optimizePropertyLookup } from './dom/prop'
@@ -304,7 +304,6 @@ function createVDOMComponent(
     rawSlots as RawSlots,
     parentComponent ? parentComponent.appContext : undefined,
     undefined,
-    parentComponent,
   )
 
   // overwrite how the vdom instance handles props
@@ -351,7 +350,9 @@ function createVDOMComponent(
     frag.nodes = vnode.el as any
   }
 
-  vnode.scopeId = (currentInstance && currentInstance.type.__scopeId) || null
+  // Use currentSlotOwner for scopeId when inside a slot
+  const scopeOwner = getSlotInstance()
+  vnode.scopeId = (scopeOwner && scopeOwner.type.__scopeId) || null
   vnode.slotScopeIds = currentSlotScopeIds
 
   frag.insert = (parentNode, anchor, transition) => {