From becad96f033ad9d50b5bb006aefbcb0baa9ab6fb Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 12:03:09 +0800 Subject: [PATCH 1/7] refactor(runtime-vapor): use dedicated currentSlotOwner instead of changing currentInstance in withVaporCtx --- packages/runtime-vapor/src/component.ts | 64 ++++++++++--------- packages/runtime-vapor/src/componentSlots.ts | 47 ++++++++------ .../runtime-vapor/src/components/Teleport.ts | 4 +- packages/runtime-vapor/src/fragment.ts | 4 +- packages/runtime-vapor/src/vdomInterop.ts | 7 +- 5 files changed, 68 insertions(+), 58 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 9371c1a1abd..c1098e3879c 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -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]) } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index b450e077a14..273f2569ddd 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -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) diff --git a/packages/runtime-vapor/src/components/Teleport.ts b/packages/runtime-vapor/src/components/Teleport.ts index f83da4f8bda..9379cdabdbe 100644 --- a/packages/runtime-vapor/src/components/Teleport.ts +++ b/packages/runtime-vapor/src/components/Teleport.ts @@ -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__ diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts index ed0b64e4246..2c3f5554a31 100644 --- a/packages/runtime-vapor/src/fragment.ts +++ b/packages/runtime-vapor/src/fragment.ts @@ -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() diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 60b5aad1f44..64ece00d88a 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -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) => { From 39ca0774c3e97d8fd9ae0809c9a96bcc0392ab5d Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 13:51:23 +0800 Subject: [PATCH 2/7] refactor: remove manual `TransitionGroup` update triggering and the `group` flag from transition hooks. --- packages/runtime-vapor/src/apiCreateFor.ts | 7 ------ packages/runtime-vapor/src/block.ts | 2 -- .../src/components/Transition.ts | 3 +-- .../src/components/TransitionGroup.ts | 25 +------------------ 4 files changed, 2 insertions(+), 35 deletions(-) diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 1ef4d5d8bec..5e46056b3b2 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -39,7 +39,6 @@ import { isLastInsertion, resetInsertionState, } from './insertionState' -import { triggerTransitionGroupUpdate } from './components/TransitionGroup' class ForBlock extends VaporFragment { scope: EffectScope | undefined @@ -131,12 +130,6 @@ export const createFor = ( newBlocks = new Array(newLength) let isFallback = false - // trigger TransitionGroup update hooks - const transitionHooks = frag.$transition - if (transitionHooks && transitionHooks.group && isMounted) { - triggerTransitionGroupUpdate(transitionHooks) - } - const prevSub = setActiveSub() if (!isMounted) { diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 3163cb92cc2..323ef7beefc 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -28,8 +28,6 @@ export interface VaporTransitionHooks extends TransitionHooks { instance: VaporComponentInstance // mark transition hooks as disabled disabled?: boolean - // indicates a group transition - group?: boolean } export interface TransitionOptions { diff --git a/packages/runtime-vapor/src/components/Transition.ts b/packages/runtime-vapor/src/components/Transition.ts index d6666e81925..5459c29b90a 100644 --- a/packages/runtime-vapor/src/components/Transition.ts +++ b/packages/runtime-vapor/src/components/Transition.ts @@ -208,7 +208,7 @@ export function applyTransitionHooks( return hooks } - const { props, instance, state, delayedLeave, group } = hooks + const { props, instance, state, delayedLeave } = hooks let resolvedHooks = resolveTransitionHooks( child, props, @@ -217,7 +217,6 @@ export function applyTransitionHooks( hooks => (resolvedHooks = hooks as VaporTransitionHooks), ) resolvedHooks.delayedLeave = delayedLeave - resolvedHooks.group = group child.$transition = resolvedHooks if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks) diff --git a/packages/runtime-vapor/src/components/TransitionGroup.ts b/packages/runtime-vapor/src/components/TransitionGroup.ts index 568c67b1f84..76d5acd1c1a 100644 --- a/packages/runtime-vapor/src/components/TransitionGroup.ts +++ b/packages/runtime-vapor/src/components/TransitionGroup.ts @@ -10,12 +10,11 @@ import { hasCSSTransform, onBeforeUpdate, onUpdated, - queuePostFlushCb, resolveTransitionProps, useTransitionState, warn, } from '@vue/runtime-dom' -import { extend, invokeArrayFns, isArray } from '@vue/shared' +import { extend, isArray } from '@vue/shared' import { type Block, type TransitionBlock, @@ -125,7 +124,6 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({ props: cssTransitionProps, state, instance, - group: true, } as VaporTransitionHooks) children = getTransitionBlocks(slottedBlock) @@ -139,7 +137,6 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({ state, instance!, ) - hooks.group = true setTransitionHooks(child, hooks) } else if (__DEV__) { warn(` children must be keyed`) @@ -216,23 +213,3 @@ function getFirstConnectedChild( if (el.isConnected) return el } } - -/** - * The implementation of TransitionGroup relies on the onBeforeUpdate and onUpdated hooks. - * However, when the slot content of TransitionGroup updates, it does not trigger the - * onBeforeUpdate and onUpdated hooks. Therefore, it is necessary to manually trigger - * the TransitionGroup update hooks to ensure its proper work. - */ -export function triggerTransitionGroupUpdate( - transition: VaporTransitionHooks, -): void { - const { instance } = transition - if (!instance.isUpdating) { - instance.isUpdating = true - if (instance.bu) invokeArrayFns(instance.bu) - queuePostFlushCb(() => { - instance.isUpdating = false - if (instance.u) invokeArrayFns(instance.u) - }) - } -} From 985209c888dc4dc39c899c4e6d25193ec64161c7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 13:56:10 +0800 Subject: [PATCH 3/7] refactor: explicitly pass current instance to fragment rendering instead of relying on a stored parentComponent property. --- packages/runtime-vapor/src/fragment.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts index 2c3f5554a31..f80f0761176 100644 --- a/packages/runtime-vapor/src/fragment.ts +++ b/packages/runtime-vapor/src/fragment.ts @@ -99,7 +99,6 @@ export class DynamicFragment extends VaporFragment { constructor(anchorLabel?: string) { super([]) - this.parentComponent = currentInstance if (isHydrating) { this.anchorLabel = anchorLabel locateHydrationNode() @@ -117,6 +116,7 @@ export class DynamicFragment extends VaporFragment { } this.current = key + const instance = currentInstance const prevSub = setActiveSub() const parent = isHydrating ? null : this.anchor.parentNode const transition = this.$transition @@ -136,7 +136,7 @@ export class DynamicFragment extends VaporFragment { const mode = transition && transition.mode if (mode) { applyTransitionLeaveHooks(this.nodes, transition, () => - this.render(render, transition, parent), + this.render(render, transition, parent, instance), ) parent && remove(this.nodes, parent) if (mode === 'out-in') { @@ -148,7 +148,7 @@ export class DynamicFragment extends VaporFragment { } } - this.render(render, transition, parent) + this.render(render, transition, parent, instance) if (this.fallback) { // set fallback for nested fragments @@ -182,6 +182,7 @@ export class DynamicFragment extends VaporFragment { render: BlockFn | undefined, transition: VaporTransitionHooks | undefined, parent: ParentNode | null, + instance: GenericComponentInstance | null, ) { if (render) { // try to reuse the kept-alive scope @@ -195,10 +196,9 @@ export class DynamicFragment extends VaporFragment { // switch current instance to parent instance during update // ensure that the parent instance is correct for nested components let prev - if (this.parentComponent && parent) - prev = setCurrentInstance(this.parentComponent) + if (parent && instance) prev = setCurrentInstance(instance) this.nodes = this.scope.run(render) || [] - if (this.parentComponent && parent) setCurrentInstance(...prev!) + if (parent && instance) setCurrentInstance(...prev!) if (transition) { this.$transition = applyTransitionHooks(this.nodes, transition) From bdba4bdf852bced7fe19b10e283d96ec441d3372 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 14:09:18 +0800 Subject: [PATCH 4/7] refactor: rename `getSlotInstance` to `getScopeOwner` and centralize scope owner retrieval --- packages/runtime-vapor/src/component.ts | 9 +++------ packages/runtime-vapor/src/componentSlots.ts | 5 ++--- packages/runtime-vapor/src/vdomInterop.ts | 5 ++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index c1098e3879c..4b59e30915c 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -77,8 +77,8 @@ import { type RawSlots, type StaticSlots, type VaporSlot, - currentSlotOwner, dynamicSlotsProxyHandlers, + getScopeOwner, getSlot, setCurrentSlotOwner, } from './componentSlots' @@ -654,9 +654,7 @@ export class VaporComponentInstance implements GenericComponentInstance { : rawSlots : EMPTY_OBJ - // 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 + const scopeOwner = getScopeOwner() this.scopeId = scopeOwner && scopeOwner.type.__scopeId // apply custom element special handling @@ -747,8 +745,7 @@ export function createPlainElement( ;(el as any).$root = isSingleRoot if (!isHydrating) { - // Use currentSlotOwner for scopeId when inside a slot - const scopeOwner = currentSlotOwner || currentInstance + const scopeOwner = getScopeOwner() const scopeId = scopeOwner!.type.__scopeId if (scopeId) setScopeId(el, [scopeId]) } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 273f2569ddd..175bcae3392 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -145,7 +145,7 @@ export function setCurrentSlotOwner( * Get the effective slot instance for accessing rawSlots and scopeId. * Prefers currentSlotOwner (if inside a slot), falls back to currentInstance. */ -export function getSlotInstance(): VaporComponentInstance { +export function getScopeOwner(): VaporComponentInstance { return (currentSlotOwner || currentInstance) as VaporComponentInstance } @@ -180,8 +180,7 @@ export function createSlot( const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() - // Use slot owner if inside a slot (forwarded slots), otherwise use currentInstance - const instance = getSlotInstance() + const instance = getScopeOwner() const rawSlots = instance.rawSlots const slotProps = rawProps ? new Proxy(rawProps, rawPropsProxyHandlers) diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 64ece00d88a..a8ede59508a 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -60,7 +60,7 @@ import { } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' -import { currentSlotScopeIds, getSlotInstance } from './componentSlots' +import { currentSlotScopeIds, getScopeOwner } from './componentSlots' import { renderEffect } from './renderEffect' import { _next, createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' @@ -350,8 +350,7 @@ function createVDOMComponent( frag.nodes = vnode.el as any } - // Use currentSlotOwner for scopeId when inside a slot - const scopeOwner = getSlotInstance() + const scopeOwner = getScopeOwner() vnode.scopeId = (scopeOwner && scopeOwner.type.__scopeId) || null vnode.slotScopeIds = currentSlotScopeIds From b3142a76c4be68735dc1dfd22676e5b8f492ec46 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 14:48:18 +0800 Subject: [PATCH 5/7] refactor: conditionally generate `withVaporCtx` for slots only when they contain components or slot outlets. --- .../__snapshots__/compile.spec.ts.snap | 6 +- .../__snapshots__/scopeId.spec.ts.snap | 26 +- .../compiler-vapor/__tests__/scopeId.spec.ts | 10 +- .../TransformTransition.spec.ts.snap | 36 +- .../__snapshots__/vSlot.spec.ts.snap | 378 ++++++++++++++---- .../__tests__/transforms/vSlot.spec.ts | 183 ++++++++- .../src/generators/component.ts | 92 ++++- .../__tests__/apiSetupContext.spec.ts | 5 +- .../__tests__/componentAttrs.spec.ts | 5 +- .../__tests__/componentSlots.spec.ts | 78 ++-- .../__tests__/components/Teleport.spec.ts | 5 +- .../__tests__/customElement.spec.ts | 14 +- .../__tests__/dom/templateRef.spec.ts | 13 +- .../__tests__/helpers/useCssVars.spec.ts | 7 +- .../runtime-vapor/__tests__/scopeId.spec.ts | 4 +- 15 files changed, 657 insertions(+), 205 deletions(-) diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index cb7b5de8683..56a1e8c726a 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -316,17 +316,17 @@ export function render(_ctx) { `; exports[`compile > expression parsing > v-slot 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { - "foo": _withVaporCtx((_slotProps0) => { + "foo": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.a + _slotProps0.b))) return n0 - }) + } }, true) return n1 }" diff --git a/packages/compiler-vapor/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/scopeId.spec.ts.snap index c49073973a1..5a399581868 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -1,23 +1,23 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`scopeId compiler support > should wrap default slot 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("
") export function render(_ctx) { const _component_Child = _resolveComponent("Child") const n1 = _createComponentWithFallback(_component_Child, null, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = t0() return n0 - }) + } }, true) return n1 }" `; exports[`scopeId compiler support > should wrap dynamic slots 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("
") export function render(_ctx) { @@ -27,18 +27,18 @@ export function render(_ctx) { () => (_ctx.ok ? { name: "foo", - fn: _withVaporCtx(() => { + fn: () => { const n0 = t0() return n0 - }) + } } : void 0), () => (_createForSlots(_ctx.list, (i) => ({ name: i, - fn: _withVaporCtx(() => { + fn: () => { const n2 = t0() return n2 - }) + } }))) ] }, true) @@ -47,22 +47,22 @@ export function render(_ctx) { `; exports[`scopeId compiler support > should wrap named slots 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") const t1 = _template("
") export function render(_ctx) { const _component_Child = _resolveComponent("Child") const n4 = _createComponentWithFallback(_component_Child, null, { - "foo": _withVaporCtx((_slotProps0) => { + "foo": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.msg))) return n0 - }), - "bar": _withVaporCtx(() => { + }, + "bar": () => { const n2 = t1() return n2 - }) + } }, true) return n4 }" diff --git a/packages/compiler-vapor/__tests__/scopeId.spec.ts b/packages/compiler-vapor/__tests__/scopeId.spec.ts index 8dfcd0c0d3d..a35d57deeab 100644 --- a/packages/compiler-vapor/__tests__/scopeId.spec.ts +++ b/packages/compiler-vapor/__tests__/scopeId.spec.ts @@ -18,7 +18,7 @@ function compile(template: string | RootNode, options: CompilerOptions = {}) { describe('scopeId compiler support', () => { test('should wrap default slot', () => { const code = compile(`
`) - expect(code).toMatch(`"default": _withVaporCtx(() => {`) + expect(code).toMatch(`"default": () => {`) expect(code).toMatchSnapshot() }) @@ -34,8 +34,8 @@ describe('scopeId compiler support', () => { scopeId: 'test', }, ) - expect(code).toMatch(`"foo": _withVaporCtx((_slotProps0) => {`) - expect(code).toMatch(`"bar": _withVaporCtx(() => {`) + expect(code).toMatch(`"foo": (_slotProps0) => {`) + expect(code).toMatch(`"bar": () => {`) expect(code).toMatchSnapshot() }) @@ -51,8 +51,8 @@ describe('scopeId compiler support', () => { scopeId: 'test', }, ) - expect(code).toMatch(/name: "foo",\s+fn: _withVaporCtx\(/) - expect(code).toMatch(/name: i,\s+fn: _withVaporCtx\(/) + expect(code).toMatch(/name: "foo",\s+fn: \(/) + expect(code).toMatch(/name: i,\s+fn: \(/) expect(code).toMatchSnapshot() }) }) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap index 9a1a369c294..a621f5a6ec0 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@ -1,46 +1,46 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: transition > basic 1`] = ` -"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("

foo

") export function render(_ctx) { const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = t0() _applyVShow(n0, () => (_ctx.show)) return n0 - }) + } }, true) return n1 }" `; exports[`compiler: transition > inject persisted when child has v-show 1`] = ` -"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("
") export function render(_ctx) { const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = t0() _applyVShow(n0, () => (_ctx.ok)) return n0 - }) + } }, true) return n1 }" `; exports[`compiler: transition > the v-if/else-if/else branches in Transition should ignore comments 1`] = ` -"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("
hey
") const t1 = _template("

") const t2 = _template("
") export function render(_ctx) { const n16 = _createComponent(_VaporTransition, null, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = _createIf(() => (_ctx.a), () => { const n2 = t0() n2.$key = 2 @@ -63,14 +63,14 @@ export function render(_ctx) { return n14 })) return [n0, n3, n7] - }) + } }, true) return n16 }" `; exports[`compiler: transition > v-show + appear 1`] = ` -"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("

foo

") export function render(_ctx) { @@ -79,11 +79,11 @@ export function render(_ctx) { appear: () => (""), persisted: () => ("") }, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = t0() deferredApplyVShows.push(() => _applyVShow(n0, () => (_ctx.show))) return n0 - }) + } }, true) deferredApplyVShows.forEach(fn => fn()) return n1 @@ -91,37 +91,37 @@ export function render(_ctx) { `; exports[`compiler: transition > work with dynamic keyed children 1`] = ` -"import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("

foo

") export function render(_ctx) { const n1 = _createComponent(_VaporTransition, null, { - "default": _withVaporCtx(() => { + "default": () => { return _createKeyedFragment(() => _ctx.key, () => { const n0 = t0() n0.$key = _ctx.key return n0 }) - }) + } }, true) return n1 }" `; exports[`compiler: transition > work with v-if 1`] = ` -"import { VaporTransition as _VaporTransition, createIf as _createIf, withVaporCtx as _withVaporCtx, createComponent as _createComponent, template as _template } from 'vue'; +"import { VaporTransition as _VaporTransition, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("

foo

") export function render(_ctx) { const n3 = _createComponent(_VaporTransition, null, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = _createIf(() => (_ctx.show), () => { const n2 = t0() n2.$key = 2 return n2 }) return n0 - }) + } }, true) return n3 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 95c90354c6a..12decc8b821 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: transform slot > dynamic slots name 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("foo") export function render(_ctx) { @@ -10,10 +10,10 @@ export function render(_ctx) { $: [ () => ({ name: _ctx.name, - fn: _withVaporCtx(() => { + fn: () => { const n0 = t0() return n0 - }) + } }) ] }, true) @@ -22,7 +22,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { @@ -31,11 +31,11 @@ export function render(_ctx) { $: [ () => (_createForSlots(_ctx.list, (item) => ({ name: item, - fn: _withVaporCtx((_slotProps0) => { + fn: (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.bar))) return n0 - }) + } }))) ] }, true) @@ -44,7 +44,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-for and provide absent key 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("foo") export function render(_ctx) { @@ -53,10 +53,10 @@ export function render(_ctx) { $: [ () => (_createForSlots(_ctx.list, (_, __, index) => ({ name: index, - fn: _withVaporCtx(() => { + fn: () => { const n0 = t0() return n0 - }) + } }))) ] }, true) @@ -65,7 +65,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("condition slot") const t1 = _template("another condition") const t2 = _template("else condition") @@ -77,25 +77,25 @@ export function render(_ctx) { () => (_ctx.condition ? { name: "condition", - fn: _withVaporCtx(() => { + fn: () => { const n0 = t0() return n0 - }) + } } : _ctx.anotherCondition ? { name: "condition", - fn: _withVaporCtx((_slotProps0) => { + fn: (_slotProps0) => { const n2 = t1() return n2 - }) + } } : { name: "condition", - fn: _withVaporCtx(() => { + fn: () => { const n4 = t2() return n4 - }) + } }) ] }, true) @@ -190,23 +190,23 @@ export function render(_ctx) { `; exports[`compiler: transform slot > implicit default slot 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("
") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx(() => { + "default": () => { const n0 = t0() return n0 - }) + } }, true) return n1 }" `; exports[`compiler: transform slot > named slots w/ implicit default slot 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("foo") const t1 = _template("bar") const t2 = _template("") @@ -214,30 +214,30 @@ const t2 = _template("") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n4 = _createComponentWithFallback(_component_Comp, null, { - "one": _withVaporCtx(() => { + "one": () => { const n0 = t0() return n0 - }), - "default": _withVaporCtx(() => { + }, + "default": () => { const n2 = t1() const n3 = t2() return [n2, n3] - }) + } }, true) return n4 }" `; exports[`compiler: transform slot > nested component should not inherit parent slots 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, withVaporCtx as _withVaporCtx } from 'vue'; export function render(_ctx) { const _component_Bar = _resolveComponent("Bar") const _component_Foo = _resolveComponent("Foo") const n2 = _createComponentWithFallback(_component_Foo, null, { - "header": _withVaporCtx(() => { + "header": () => { return null - }), + }, "default": _withVaporCtx(() => { const n1 = _createComponentWithFallback(_component_Bar) return n1 @@ -264,7 +264,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > nested slots scoping 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, withVaporCtx as _withVaporCtx, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { @@ -273,11 +273,11 @@ export function render(_ctx) { const n5 = _createComponentWithFallback(_component_Comp, null, { "default": _withVaporCtx((_slotProps0) => { const n1 = _createComponentWithFallback(_component_Inner, null, { - "default": _withVaporCtx((_slotProps1) => { + "default": (_slotProps1) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo + _slotProps1.bar + _ctx.baz))) return n0 - }) + } }) const n3 = t0() _renderEffect(() => _setText(n3, " " + _toDisplayString(_slotProps0.foo + _ctx.bar + _ctx.baz))) @@ -289,7 +289,7 @@ export function render(_ctx) { `; exports[`compiler: transform slot > on component dynamically named slot 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { @@ -298,11 +298,11 @@ export function render(_ctx) { $: [ () => ({ name: _ctx.named, - fn: _withVaporCtx((_slotProps0) => { + fn: (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo + _ctx.bar))) return n0 - }) + } }) ] }, true) @@ -311,48 +311,48 @@ export function render(_ctx) { `; exports[`compiler: transform slot > on component named slot 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { - "named": _withVaporCtx((_slotProps0) => { + "named": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo + _ctx.bar))) return n0 - }) + } }, true) return n1 }" `; exports[`compiler: transform slot > on-component default slot 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo + _ctx.bar))) return n0 - }) + } }, true) return n1 }" `; exports[`compiler: transform slot > quote slot name 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = _createComponentWithFallback(_component_Comp, null, { - "nav-bar-title-before": _withVaporCtx(() => { + "nav-bar-title-before": () => { return null - }) + } }, true) return n1 }" @@ -381,143 +381,143 @@ export function render(_ctx) { `; exports[`compiler: transform slot > slot prop alias uses original key 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.msg))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop array rest destructuring 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.arr.slice(1)[0]))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop computed key destructuring 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0[_ctx.key]))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop default value 1`] = ` -"import { resolveComponent as _resolveComponent, getDefaultValue as _getDefaultValue, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, getDefaultValue as _getDefaultValue, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_getDefaultValue(_slotProps0.foo, 1)))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop nested default value 1`] = ` -"import { resolveComponent as _resolveComponent, getDefaultValue as _getDefaultValue, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, getDefaultValue as _getDefaultValue, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_getDefaultValue(_slotProps0.foo[0], 1) + _getDefaultValue(_slotProps0.baz.qux, 2)))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop nested destructuring 1`] = ` -"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo.bar))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop rest destructuring 1`] = ` -"import { resolveComponent as _resolveComponent, getRestElement as _getRestElement, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, getRestElement as _getRestElement, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_getRestElement(_slotProps0, ["foo"]).bar))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > slot prop rest with computed keys preserved 1`] = ` -"import { resolveComponent as _resolveComponent, getRestElement as _getRestElement, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, getRestElement as _getRestElement, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n2 = _createComponentWithFallback(_component_Comp, null, { - "default": _withVaporCtx((_slotProps0) => { + "default": (_slotProps0) => { const n0 = t0() _renderEffect(() => _setText(n0, _toDisplayString(_slotProps0.foo + _getRestElement(_slotProps0, ["foo", _ctx.key]).other))) return n0 - }) + } }, true) return n2 }" `; exports[`compiler: transform slot > with whitespace: 'preserve' > implicit default slot 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" Header ") const t1 = _template(" ") const t2 = _template("

") @@ -525,58 +525,276 @@ const t2 = _template("

") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n4 = _createComponentWithFallback(_component_Comp, null, { - "header": _withVaporCtx(() => { + "header": () => { const n0 = t0() return n0 - }), - "default": _withVaporCtx(() => { + }, + "default": () => { const n2 = t1() const n3 = t2() return [n2, n3] - }) + } }, true) return n4 }" `; exports[`compiler: transform slot > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" Header ") const t1 = _template(" Default ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n5 = _createComponentWithFallback(_component_Comp, null, { - "header": _withVaporCtx(() => { + "header": () => { const n0 = t0() return n0 - }), - "default": _withVaporCtx(() => { + }, + "default": () => { const n3 = t1() return n3 - }) + } }, true) return n5 }" `; exports[`compiler: transform slot > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = ` -"import { resolveComponent as _resolveComponent, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template(" Header ") const t1 = _template(" Footer ") export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n5 = _createComponentWithFallback(_component_Comp, null, { - "header": _withVaporCtx(() => { + "header": () => { const n0 = t0() return n0 - }), - "footer": _withVaporCtx(() => { + }, + "footer": () => { const n3 = t1() return n3 + } + }, true) + return n5 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with component inside v-for should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, createFor as _createFor, withVaporCtx as _withVaporCtx, template as _template } from 'vue'; +const t0 = _template("
") + +export function render(_ctx) { + const _component_ChildComp = _resolveComponent("ChildComp") + const _component_Comp = _resolveComponent("Comp") + const n5 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createFor(() => (_ctx.items), (_for_item0) => { + const n3 = t0() + _setInsertionState(n3, null, true) + const n2 = _createComponentWithFallback(_component_ChildComp) + return n3 + }) + return n0 + }) + }, true) + return n5 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with component inside v-if should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, createIf as _createIf, withVaporCtx as _withVaporCtx, template as _template } from 'vue'; +const t0 = _template("
") + +export function render(_ctx) { + const _component_ChildComp = _resolveComponent("ChildComp") + const _component_Comp = _resolveComponent("Comp") + const n5 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createIf(() => (_ctx.show), () => { + const n3 = t0() + _setInsertionState(n3, null, true) + const n2 = _createComponentWithFallback(_component_ChildComp) + return n3 + }) + return n0 + }) + }, true) + return n5 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with component should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, withVaporCtx as _withVaporCtx } from 'vue'; + +export function render(_ctx) { + const _component_ChildComp = _resolveComponent("ChildComp") + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createComponentWithFallback(_component_ChildComp) + return n0 + }) + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with custom element inside v-if should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createPlainElement as _createPlainElement, createIf as _createIf, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +const t0 = _template("
") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n5 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createIf(() => (_ctx.show), () => { + const n3 = t0() + _setInsertionState(n3, null, true) + const n2 = _createPlainElement("my-element") + return n3 + }) + return n0 }) }, true) return n5 }" `; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with custom element should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, createPlainElement as _createPlainElement, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createPlainElement("my-element") + return n0 + }) + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with nested v-if containing component should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, createIf as _createIf, withVaporCtx as _withVaporCtx, template as _template } from 'vue'; +const t0 = _template("") +const t1 = _template("
") + +export function render(_ctx) { + const _component_ChildComp = _resolveComponent("ChildComp") + const _component_Comp = _resolveComponent("Comp") + const n8 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createIf(() => (_ctx.a), () => { + const n6 = t1() + _setInsertionState(n6, null, true) + const n2 = _createIf(() => (_ctx.b), () => { + const n5 = t0() + _setInsertionState(n5, null, true) + const n4 = _createComponentWithFallback(_component_ChildComp) + return n5 + }) + return n6 + }) + return n0 + }) + }, true) + return n8 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with only static elements should not have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +const t0 = _template("
static content
") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = t0() + return n0 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with only text interpolation should not have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +const t0 = _template(" ") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = t0() + _renderEffect(() => _setText(n0, _toDisplayString(_ctx.message))) + return n0 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with slot outlet should have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, createSlot as _createSlot, withVaporCtx as _withVaporCtx, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": _withVaporCtx(() => { + const n0 = _createSlot("default", null) + return n0 + }) + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with v-for but no component should not have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +const t0 = _template("
") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n4 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createFor(() => (_ctx.items), (_for_item0) => { + const n2 = t0() + const x2 = _txt(n2) + _renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value))) + return n2 + }) + const x0 = _txt(n0) + _renderEffect(() => _setText(x0, _toDisplayString(_ctx.item))) + return n0 + } + }, true) + return n4 +}" +`; + +exports[`compiler: transform slot > withVaporCtx optimization > slot with v-if but no component should not have withVaporCtx 1`] = ` +"import { resolveComponent as _resolveComponent, createIf as _createIf, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; +const t0 = _template("
content
") +const t1 = _template("fallback") + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n7 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createIf(() => (_ctx.show), () => { + const n2 = t0() + return n2 + }, () => { + const n4 = t1() + return n4 + }) + return n0 + } + }, true) + return n7 +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index b70ee26970b..89bdc335aa5 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -67,7 +67,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_slotProps0.foo + _ctx.bar`) expect(ir.block.dynamic.children[0].operation).toMatchObject({ @@ -101,7 +101,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"named": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"named": (_slotProps0) =>`) expect(code).contains(`_slotProps0.foo + _ctx.bar`) expect(ir.block.dynamic.children[0].operation).toMatchObject({ @@ -130,7 +130,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`fn: _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`fn: (_slotProps0) =>`) expect(code).contains(`_slotProps0.foo + _ctx.bar`) expect(ir.block.dynamic.children[0].operation).toMatchObject({ @@ -171,7 +171,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_slotProps0.msg`) }) @@ -181,7 +181,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_slotProps0.foo.bar`) }) @@ -191,7 +191,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_slotProps0[_ctx.key]`) }) @@ -201,7 +201,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_getRestElement(_slotProps0`) }) @@ -211,7 +211,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_slotProps0.arr.slice(1)`) }) @@ -221,7 +221,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_getDefaultValue(_slotProps0.foo, 1)`) }) @@ -231,7 +231,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_getDefaultValue(_slotProps0.foo[0], 1)`) expect(code).contains(`_getDefaultValue(_slotProps0.baz.qux, 2)`) }) @@ -242,7 +242,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`"default": (_slotProps0) =>`) expect(code).contains(`_getRestElement(_slotProps0, ["foo", _ctx.key])`) }) @@ -296,7 +296,7 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() expect(code).contains(`"default": _withVaporCtx((_slotProps0) =>`) - expect(code).contains(`"default": _withVaporCtx((_slotProps1) =>`) + expect(code).contains(`"default": (_slotProps1) =>`) expect(code).contains(`_slotProps0.foo + _slotProps1.bar + _ctx.baz`) expect(code).contains(`_slotProps0.foo + _ctx.bar + _ctx.baz`) @@ -373,7 +373,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`fn: _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`fn: (_slotProps0) =>`) expect(code).contains(`_setText(n0, _toDisplayString(_slotProps0.bar))`) expect(ir.block.dynamic.children[0].operation).toMatchObject({ @@ -437,7 +437,7 @@ describe('compiler: transform slot', () => { ) expect(code).toMatchSnapshot() - expect(code).contains(`fn: _withVaporCtx((_slotProps0) =>`) + expect(code).contains(`fn: (_slotProps0) =>`) expect(ir.block.dynamic.children[0].operation).toMatchObject({ type: IRNodeTypes.CREATE_COMPONENT_NODE, @@ -685,4 +685,159 @@ describe('compiler: transform slot', () => { expect(code).toMatchSnapshot() }) }) + + describe('withVaporCtx optimization', () => { + test('slot with only static elements should not have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).not.toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with component should have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with slot outlet should have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with component inside v-if should have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with component inside v-for should have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with nested v-if containing component should have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with only text interpolation should not have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).not.toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with v-if but no component should not have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).not.toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with v-for but no component should not have withVaporCtx', () => { + const { code } = compileWithSlots(` + + + + `) + expect(code).not.toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with custom element should have withVaporCtx', () => { + const { code } = compileWithSlots( + ` + + + + `, + { + isCustomElement: tag => tag.startsWith('my-'), + }, + ) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + + test('slot with custom element inside v-if should have withVaporCtx', () => { + const { code } = compileWithSlots( + ` + + + + `, + { + isCustomElement: tag => tag.startsWith('my-'), + }, + ) + expect(code).toContain('withVaporCtx') + expect(code).toMatchSnapshot() + }) + }) }) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index c4e9061345d..37af449eb0c 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -1,8 +1,12 @@ import { camelize, extend, getModifierPropName, isArray } from '@vue/shared' import type { CodegenContext } from '../generate' import { + type BlockIRNode, type CreateComponentIRNode, + type ForIRNode, + type IRDynamicInfo, IRDynamicPropsKind, + IRNodeTypes, type IRProp, type IRProps, type IRPropsStatic, @@ -13,6 +17,8 @@ import { IRSlotType, type IRSlots, type IRSlotsStatic, + type IfIRNode, + type OperationNode, type SlotBlockIRNode, } from '../ir' import { @@ -45,7 +51,7 @@ import { parseValueDestructure, } from './for' import { genModelHandler } from './vModel' -import { isBuiltInComponent, isKeepAliveTag } from '../utils' +import { isBuiltInComponent } from '../utils' export function genCreateComponent( operation: CreateComponentIRNode, @@ -462,10 +468,88 @@ function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) { ] } - if (node.type === NodeTypes.ELEMENT && !isKeepAliveTag(node.tag)) { - // wrap with withVaporCtx to ensure correct currentInstance inside slot - blockFn = [`${context.helper('withVaporCtx')}(`, ...blockFn, `)`] + if (node.type === NodeTypes.ELEMENT) { + // wrap with withVaporCtx to track slot owner for: + // 1. createSlot to get correct rawSlots in forwarded slots + // 2. scopeId inheritance for components created inside slots + // Skip if slot content has no components or slot outlets + if (needsVaporCtx(oper)) { + blockFn = [`${context.helper('withVaporCtx')}(`, ...blockFn, `)`] + } } return blockFn } + +/** + * Check if a slot block needs withVaporCtx wrapper. + * Returns true if the block contains: + * - Component creation (needs scopeId inheritance) + * - Slot outlet (needs rawSlots from slot owner) + */ +function needsVaporCtx(block: BlockIRNode): boolean { + return hasComponentOrSlotInBlock(block) +} + +function hasComponentOrSlotInBlock(block: BlockIRNode): boolean { + // Check operations array + if (hasComponentOrSlotInOperations(block.operation)) return true + // Check dynamic children (components are often stored here) + return hasComponentOrSlotInDynamic(block.dynamic) +} + +function hasComponentOrSlotInDynamic(dynamic: IRDynamicInfo): boolean { + // Check operation in this dynamic node + if (dynamic.operation) { + const type = dynamic.operation.type + if ( + type === IRNodeTypes.CREATE_COMPONENT_NODE || + type === IRNodeTypes.SLOT_OUTLET_NODE + ) { + return true + } + if (type === IRNodeTypes.IF) { + if (hasComponentOrSlotInIf(dynamic.operation as IfIRNode)) return true + } + if (type === IRNodeTypes.FOR) { + if (hasComponentOrSlotInBlock((dynamic.operation as ForIRNode).render)) + return true + } + } + // Recursively check children + for (const child of dynamic.children) { + if (hasComponentOrSlotInDynamic(child)) return true + } + return false +} + +function hasComponentOrSlotInOperations(operations: OperationNode[]): boolean { + for (const op of operations) { + switch (op.type) { + case IRNodeTypes.CREATE_COMPONENT_NODE: + case IRNodeTypes.SLOT_OUTLET_NODE: + return true + case IRNodeTypes.IF: + if (hasComponentOrSlotInIf(op as IfIRNode)) return true + break + case IRNodeTypes.FOR: + if (hasComponentOrSlotInBlock((op as ForIRNode).render)) return true + break + } + } + return false +} + +function hasComponentOrSlotInIf(node: IfIRNode): boolean { + if (hasComponentOrSlotInBlock(node.positive)) return true + if (node.negative) { + if ('positive' in node.negative) { + // nested IfIRNode + return hasComponentOrSlotInIf(node.negative as IfIRNode) + } else { + // BlockIRNode + return hasComponentOrSlotInBlock(node.negative as BlockIRNode) + } + } + return false +} diff --git a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts index d6942143225..5e0d5d98aa0 100644 --- a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts @@ -10,7 +10,6 @@ import { setDynamicProps, setText, template, - withVaporCtx, } from '../src' import { nextTick, reactive, ref, watchEffect } from '@vue/runtime-dom' import { makeRender } from './_utils' @@ -114,11 +113,11 @@ describe('api: setup context', () => { inheritAttrs: false, setup(_: any, { attrs }: any) { const n0 = createComponent(Wrapper, null, { - default: withVaporCtx(() => { + default: () => { const n0 = template('
')() as HTMLDivElement renderEffect(() => setDynamicProps(n0, [attrs])) return n0 - }), + }, }) return n0 }, diff --git a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts index d82837ffa7e..e5838e91ffe 100644 --- a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts +++ b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts @@ -20,7 +20,6 @@ import { setProp, setStyle, template, - withVaporCtx, } from '../src' import { makeRender } from './_utils' import { stringifyStyle } from '@vue/shared' @@ -1087,10 +1086,10 @@ describe('attribute fallthrough', () => { () => 'button', null, { - default: withVaporCtx(() => { + default: () => { const n0 = createSlot('default', null) return n0 - }), + }, }, true, ) diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 74b6a9b3b9a..db2171b583a 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -178,7 +178,7 @@ describe('component: slots', () => { const { host } = define(() => { return createComponent(Comp, null, { - header: withVaporCtx(() => template('header')()), + header: () => template('header')(), }) }).render() @@ -225,7 +225,7 @@ describe('component: slots', () => { ) define(() => createComponent(Comp, null, { - default: withVaporCtx((_props: any) => ((props = _props), [])), + default: (_props: any) => ((props = _props), []), }), ).render() @@ -253,7 +253,7 @@ describe('component: slots', () => { ) define(() => createComponent(Comp, null, { - default: withVaporCtx((_props: any) => ((props = _props), [])), + default: (_props: any) => ((props = _props), []), }), ).render() @@ -286,13 +286,13 @@ describe('component: slots', () => { $: [ () => ({ name: 'header', - fn: withVaporCtx((props: any) => { + fn: (props: any) => { const el = template('

')() renderEffect(() => { setElementText(el, props.title) }) return el - }), + }, }), ], }) @@ -321,8 +321,8 @@ describe('component: slots', () => { const { host } = define(() => { return createComponent(Comp, null, { - header: withVaporCtx(() => template('header')()), - footer: withVaporCtx(() => template('footer')()), + header: () => template('header')(), + footer: () => template('footer')(), }) }).render() @@ -371,11 +371,11 @@ describe('component: slots', () => { flag1.value ? { name: 'one', - fn: withVaporCtx(() => template('one content')()), + fn: () => template('one content')(), } : { name: 'two', - fn: withVaporCtx(() => template('two content')()), + fn: () => template('two content')(), }, ], }) @@ -420,8 +420,8 @@ describe('component: slots', () => { Child, {}, { - one: withVaporCtx(() => template('one content')()), - two: withVaporCtx(() => template('two content')()), + one: () => template('one content')(), + two: () => template('two content')(), }, ) }).render() @@ -468,14 +468,14 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) - }), + }, }) }, }).render() @@ -505,14 +505,14 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) - }), + }, }) }, }).render() @@ -546,9 +546,9 @@ describe('component: slots', () => { (toggle.value ? { name: val.value, - fn: withVaporCtx(() => { + fn: () => { return template('

')() - }), + }, } : void 0) as DynamicSlot, ], @@ -574,9 +574,9 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { return template('')() - }), + }, }) }, }).render() @@ -598,14 +598,14 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) - }), + }, }) }, }).render() @@ -636,7 +636,7 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { return createIf( () => outerShow.value, () => { @@ -648,7 +648,7 @@ describe('component: slots', () => { ) }, ) - }), + }, }) }, }).render() @@ -693,7 +693,7 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { const n2 = createFor( () => items.value, for_item0 => { @@ -706,7 +706,7 @@ describe('component: slots', () => { }, ) return n2 - }), + }, }) }, }).render() @@ -739,7 +739,7 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { const n2 = createFor( () => items.value, for_item0 => { @@ -752,7 +752,7 @@ describe('component: slots', () => { }, ) return n2 - }), + }, }) }, }).render() @@ -794,12 +794,12 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { const n3 = template('
')() as any const x3 = txt(n3) as any renderEffect(() => setText(x3, toDisplayString(count.value))) return n3 - }), + }, }) }, }).render() @@ -843,11 +843,11 @@ describe('component: slots', () => { Parent, null, { - foo: withVaporCtx(() => { + foo: () => { const n0 = template(' ')() as any renderEffect(() => setText(n0, foo.value)) return n0 - }), + }, }, true, ) @@ -888,16 +888,16 @@ describe('component: slots', () => { Parent, null, { - foo: withVaporCtx(() => { + foo: () => { const n0 = template(' ')() as any renderEffect(() => setText(n0, foo.value)) return n0 - }), - default: withVaporCtx(() => { + }, + default: () => { const n3 = template(' ')() as any renderEffect(() => setText(n3, foo.value)) return n3 - }), + }, }, true, ) @@ -936,7 +936,7 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Parent, null, { - default: withVaporCtx(() => template('')()), + default: () => template('')(), }) }, }).render() @@ -976,7 +976,7 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Parent, null, { - default: withVaporCtx(() => template('')()), + default: () => template('')(), }) }, }).render() @@ -1057,8 +1057,8 @@ describe('component: slots', () => { const { html } = define({ setup() { return createComponent(Child, null, { - default: withVaporCtx(() => template('default')()), - foo: withVaporCtx(() => template('foo')()), + default: () => template('default')(), + foo: () => template('foo')(), }) }, }).render() diff --git a/packages/runtime-vapor/__tests__/components/Teleport.spec.ts b/packages/runtime-vapor/__tests__/components/Teleport.spec.ts index f6b47220e76..c8f77f29af9 100644 --- a/packages/runtime-vapor/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-vapor/__tests__/components/Teleport.spec.ts @@ -4,7 +4,6 @@ import { createComponent as createComp, createComponent, } from '../../src/component' -import { withVaporCtx } from '../../src' import { type VaporDirective, VaporTeleport, @@ -719,11 +718,11 @@ function runSharedTests(deferMode: boolean): void { VaporTeleport, { to: () => svg.value }, { - default: withVaporCtx(() => { + default: () => { const n3 = template('', false, 1)() as any _setTemplateRef(n3, circle, undefined, undefined, 'circle') return n3 - }), + }, }, ) return n4 diff --git a/packages/runtime-vapor/__tests__/customElement.spec.ts b/packages/runtime-vapor/__tests__/customElement.spec.ts index faad57aa52b..fecbf51780b 100644 --- a/packages/runtime-vapor/__tests__/customElement.spec.ts +++ b/packages/runtime-vapor/__tests__/customElement.spec.ts @@ -1407,7 +1407,7 @@ describe('defineVaporCustomElement', () => { return createPlainElement('my-parent', null, { default: withVaporCtx(() => createPlainElement('my-child', null, { - default: withVaporCtx(() => template('default')()), + default: () => template('default')(), }), ), }) @@ -1461,7 +1461,7 @@ describe('defineVaporCustomElement', () => { return createPlainElement('my-el-teleport-parent', null, { default: withVaporCtx(() => createPlainElement('my-el-teleport-child', null, { - default: withVaporCtx(() => template('default')()), + default: () => template('default')(), }), ), }) @@ -1505,10 +1505,10 @@ describe('defineVaporCustomElement', () => { const App = { setup() { return createPlainElement('my-el-two-teleport-child', null, { - default: withVaporCtx(() => [ + default: () => [ template('
header
')(), template('body')(), - ]), + ], }) }, } @@ -1556,10 +1556,10 @@ describe('defineVaporCustomElement', () => { const App = { setup() { return createPlainElement('my-el-two-teleport-child-0', null, { - default: withVaporCtx(() => [ + default: () => [ template('
header
')(), template('body')(), - ]), + ], }) }, } @@ -1591,7 +1591,7 @@ describe('defineVaporCustomElement', () => { const ChildWrapper = { setup() { return createPlainElement('my-el-child-shadow-false', null, { - default: withVaporCtx(() => template('child')()), + default: () => template('child')(), }) }, } diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 536ae965eb3..c86eb48de08 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -12,7 +12,6 @@ import { insert, renderEffect, template, - withVaporCtx, } from '../../src' import { compile, makeRender, runtimeDom, runtimeVapor } from '../_utils' import { @@ -613,11 +612,11 @@ describe('api: template ref', () => { render() { const setRef = createTemplateRefSetter() const n0 = createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { n = document.createElement('div') setRef(n, 'foo') return n - }), + }, }) return n0 }, @@ -641,11 +640,11 @@ describe('api: template ref', () => { setup() { const setRef = createTemplateRefSetter() const n0 = createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { n = document.createElement('div') setRef(n, r) return n - }), + }, }) return n0 }, @@ -670,11 +669,11 @@ describe('api: template ref', () => { r = useTemplateRef('foo') const setRef = createTemplateRefSetter() const n0 = createComponent(Child, null, { - default: withVaporCtx(() => { + default: () => { n = document.createElement('div') setRef(n, 'foo') return n - }), + }, }) return n0 }, diff --git a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts index 1f73df6e0a8..0c5b50725c1 100644 --- a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts +++ b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts @@ -9,7 +9,6 @@ import { setStyle, template, useVaporCssVars, - withVaporCtx, } from '@vue/runtime-vapor' import { nextTick, onMounted, reactive, ref } from '@vue/runtime-core' import { makeRender } from '../_utils' @@ -246,14 +245,14 @@ describe('useVaporCssVars', () => { VaporTeleport, { to: () => target }, { - default: withVaporCtx(() => { + default: () => { const n0 = template('
', true)() const n1 = createIf( () => toggle.value, () => template('
', true)(), ) return [n0, n1] - }), + }, }, ) }, @@ -286,7 +285,7 @@ describe('useVaporCssVars', () => { VaporTeleport, { to: () => target, disabled: () => true }, { - default: withVaporCtx(() => template('
', true)()), + default: () => template('
', true)(), }, ) }, diff --git a/packages/runtime-vapor/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts index 4689c1b6b7d..04b54e7d1cb 100644 --- a/packages/runtime-vapor/__tests__/scopeId.spec.ts +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@ -293,9 +293,9 @@ describe('scopeId', () => { Slotted, null, { - default: withVaporCtx(() => { + default: () => { return template('
')() - }), + }, }, true, ) From 34f87f23f219e33561fb8ee482e794af01e992ca Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 15:22:22 +0800 Subject: [PATCH 6/7] refactor: extract scope ID retrieval into `getCurrentScopeId` helper. --- packages/runtime-vapor/src/component.ts | 11 +++++++---- packages/runtime-vapor/src/vdomInterop.ts | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 4b59e30915c..169c08ad5dd 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -654,8 +654,7 @@ export class VaporComponentInstance implements GenericComponentInstance { : rawSlots : EMPTY_OBJ - const scopeOwner = getScopeOwner() - this.scopeId = scopeOwner && scopeOwner.type.__scopeId + this.scopeId = getCurrentScopeId() // apply custom element special handling if (comp.ce) { @@ -745,8 +744,7 @@ export function createPlainElement( ;(el as any).$root = isSingleRoot if (!isHydrating) { - const scopeOwner = getScopeOwner() - const scopeId = scopeOwner!.type.__scopeId + const scopeId = getCurrentScopeId() if (scopeId) setScopeId(el, [scopeId]) } @@ -1031,3 +1029,8 @@ function handleSetupResult( popWarningContext() } } + +export function getCurrentScopeId(): string | undefined { + const scopeOwner = getScopeOwner() + return scopeOwner && scopeOwner.type.__scopeId +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index a8ede59508a..c6a0d98bbcc 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -45,6 +45,7 @@ import { type VaporComponent, VaporComponentInstance, createComponent, + getCurrentScopeId, isVaporComponent, mountComponent, unmountComponent, @@ -60,7 +61,7 @@ import { } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' -import { currentSlotScopeIds, getScopeOwner } from './componentSlots' +import { currentSlotScopeIds } from './componentSlots' import { renderEffect } from './renderEffect' import { _next, createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' @@ -350,8 +351,7 @@ function createVDOMComponent( frag.nodes = vnode.el as any } - const scopeOwner = getScopeOwner() - vnode.scopeId = (scopeOwner && scopeOwner.type.__scopeId) || null + vnode.scopeId = getCurrentScopeId() || null vnode.slotScopeIds = currentSlotScopeIds frag.insert = (parentNode, anchor, transition) => { From 8b9d9d0828192efe3b28057075cad9324ea44898 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 4 Dec 2025 15:27:27 +0800 Subject: [PATCH 7/7] refactor: allow `getScopeOwner` to return null and add non-null assertion for instance access. --- packages/runtime-vapor/src/component.ts | 2 +- packages/runtime-vapor/src/componentSlots.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 169c08ad5dd..ae568ac3a1b 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1032,5 +1032,5 @@ function handleSetupResult( export function getCurrentScopeId(): string | undefined { const scopeOwner = getScopeOwner() - return scopeOwner && scopeOwner.type.__scopeId + return scopeOwner ? scopeOwner.type.__scopeId : undefined } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 175bcae3392..b0c50917f64 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -145,8 +145,8 @@ export function setCurrentSlotOwner( * Get the effective slot instance for accessing rawSlots and scopeId. * Prefers currentSlotOwner (if inside a slot), falls back to currentInstance. */ -export function getScopeOwner(): VaporComponentInstance { - return (currentSlotOwner || currentInstance) as VaporComponentInstance +export function getScopeOwner(): VaporComponentInstance | null { + return (currentSlotOwner || currentInstance) as VaporComponentInstance | null } /** @@ -180,7 +180,7 @@ export function createSlot( const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() - const instance = getScopeOwner() + const instance = getScopeOwner()! const rawSlots = instance.rawSlots const slotProps = rawProps ? new Proxy(rawProps, rawPropsProxyHandlers)