]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'edison/feat/vaporTransition' into edison/testVapor
authordaiwei <daiwei521@126.com>
Mon, 16 Jun 2025 02:44:54 +0000 (10:44 +0800)
committerdaiwei <daiwei521@126.com>
Mon, 16 Jun 2025 02:45:13 +0000 (10:45 +0800)
22 files changed:
1  2 
packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
packages/compiler-vapor/src/generators/block.ts
packages/compiler-vapor/src/generators/component.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/compiler-vapor/src/transforms/vIf.ts
packages/compiler-vapor/src/transforms/vSlot.ts
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/src/index.ts
packages/runtime-vapor/src/apiCreateDynamicComponent.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/dom/node.ts
packages/runtime-vapor/src/dom/prop.ts
packages/runtime-vapor/src/dom/template.ts
packages/runtime-vapor/src/index.ts
packages/runtime-vapor/src/vdomInterop.ts
packages/server-renderer/__tests__/webStream.spec.ts

index 0000000000000000000000000000000000000000,2cb5c36e41ec1467ec2aafb99c64430e824bf1e7..8a83143b9b720b37d326245d05c49b6779faab40
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,128 +1,128 @@@
 -"import { VaporTransition as _VaporTransition, createIf as _createIf, prepend as _prepend, createComponent as _createComponent, template as _template } from 'vue';
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+ exports[`compiler: transition > basic 1`] = `
+ "import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<h1>foo</h1>")
+ export function render(_ctx) {
+   const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+     "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, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<div></div>")
+ export function render(_ctx) {
+   const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+     "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`] = `
 -        _prepend(n14, n9)
++"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<div>hey</div>")
+ const t1 = _template("<p></p>")
+ const t2 = _template("<div></div>")
+ export function render(_ctx) {
+   const n16 = _createComponent(_VaporTransition, null, {
+     "default": () => {
+       const n0 = _createIf(() => (_ctx.a), () => {
+         const n2 = t0()
+         n2.$key = 2
+         return n2
+       }, () => _createIf(() => (_ctx.b), () => {
+         const n5 = t0()
+         n5.$key = 5
+         return n5
+       }, () => {
+         const n14 = t2()
++        _setInsertionState(n14, 0)
+         const n9 = _createIf(() => (_ctx.c), () => {
+           const n11 = t1()
+           return n11
+         }, () => {
+           const n13 = t1()
+           return n13
+         })
+         n14.$key = 14
+         return n14
+       }))
+       return [n0, n3, n7]
+     }
+   }, true)
+   return n16
+ }"
+ `;
+ exports[`compiler: transition > v-show + appear 1`] = `
+ "import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<h1>foo</h1>")
+ export function render(_ctx) {
+   const deferredApplyVShows = []
+   const n1 = _createComponent(_VaporTransition, {
+     appear: () => (""), 
+     persisted: () => ("")
+   }, {
+     "default": () => {
+       const n0 = t0()
+       deferredApplyVShows.push(() => _applyVShow(n0, () => (_ctx.show)))
+       return n0
+     }
+   }, true)
+   deferredApplyVShows.forEach(fn => fn())
+   return n1
+ }"
+ `;
+ exports[`compiler: transition > work with dynamic keyed children 1`] = `
+ "import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<h1>foo</h1>")
+ export function render(_ctx) {
+   const n1 = _createComponent(_VaporTransition, null, {
+     "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, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<h1>foo</h1>")
+ export function render(_ctx) {
+   const n3 = _createComponent(_VaporTransition, null, {
+     "default": () => {
+       const n0 = _createIf(() => (_ctx.show), () => {
+         const n2 = t0()
+         n2.$key = 2
+         return n2
+       })
+       return n0
+     }
+   }, true)
+   return n3
+ }"
+ `;
index d823c4ed0af05444d7482e9c98b0facbe444c387,f2eade4bcdf4e275f5a0b839ae9d762de1bb54a7..ced96896b9e75cabc7e9383b06db173f7fe97dc5
@@@ -43,24 -43,6 +43,15 @@@ export function render(_ctx) 
  }"
  `;
  
- exports[`compiler: template ref transform > static ref (PROD) 1`] = `
- "
-   const _setTemplateRef = _createTemplateRefSetter()
-   const n0 = t0()
-   _setTemplateRef(n0, foo)
-   return n0
- "
- `;
 +exports[`compiler: template ref transform > static ref (inline mode) 1`] = `
 +"
 +  const _setTemplateRef = _createTemplateRefSetter()
 +  const n0 = t0()
 +  _setTemplateRef(n0, foo)
 +  return n0
 +"
 +`;
 +
  exports[`compiler: template ref transform > static ref 1`] = `
  "import { createTemplateRefSetter as _createTemplateRefSetter, template as _template } from 'vue';
  const t0 = _template("<div></div>", true)
index ff240dd6eac05068f63d88df62be4e39d97ff13d,944a5ee3af9e756647fca97413ed278c1d8f15bd..0811921bd9655f2f9e61a913ce0495a80c312393
@@@ -11,8 -11,9 +11,9 @@@ import 
  } from './utils'
  import type { CodegenContext } from '../generate'
  import { genEffects, genOperations } from './operation'
 -import { genChildren } from './template'
 +import { genChildren, genSelf } from './template'
  import { toValidAssetId } from '@vue/compiler-dom'
+ import { genExpression } from './expression'
  
  export function genBlock(
    oper: BlockIRNode,
@@@ -40,25 -41,15 +41,29 @@@ export function genBlockContent
    customReturns?: (returns: CodeFragment[]) => CodeFragment[],
  ): CodeFragment[] {
    const [frag, push] = buildCodeFragment()
-   const { dynamic, effect, operation, returns } = block
+   const { dynamic, effect, operation, returns, key } = block
    const resetBlock = context.enterBlock(block)
  
+   if (block.hasDeferredVShow) {
+     push(NEWLINE, `const deferredApplyVShows = []`)
+   }
    if (root) {
 -    genResolveAssets('component', 'resolveComponent')
 +    for (let name of context.ir.component) {
 +      const id = toValidAssetId(name, 'component')
 +      const maybeSelfReference = name.endsWith('__self')
 +      if (maybeSelfReference) name = name.slice(0, -6)
 +      push(
 +        NEWLINE,
 +        `const ${id} = `,
 +        ...genCall(
 +          context.helper('resolveComponent'),
 +          JSON.stringify(name),
 +          // pass additional `maybeSelfReference` flag
 +          maybeSelfReference ? 'true' : undefined,
 +        ),
 +      )
 +    }
      genResolveAssets('directive', 'resolveDirective')
    }
  
index 7c232db754be5c6574fd04b8098eeff1e2cb368f,08d0730a564c81a68be2728ce85efac308ff7b11..d50ac58e771be74e27d15eae5c7c2ccd11ef8141
@@@ -51,9 -53,8 +53,9 @@@ export function genCreateComponent
    const rawSlots = genRawSlots(slots, context)
    const [ids, handlers] = processInlineHandlers(props, context)
    const rawProps = context.withId(() => genRawProps(props, context), ids)
 +
    const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
-     (acc, { name, value }) => {
+     (acc, { name, value }: InlineHandler) => {
        const handler = genEventHandler(context, value, undefined, false)
        return [...acc, `const ${name} = `, ...handler, NEWLINE]
      },
index 086f77ca61246d92ffbad30cf6e87cc4a72e9373,b4126863f11cfc443fa3c008a899f3cd164c5f24..3a00ba68da3ebd1be7b61cb16e16a8f8e5cfe164
@@@ -262,7 -264,7 +265,8 @@@ export interface IRDynamicInfo 
    children: IRDynamicInfo[]
    template?: number
    hasDynamicChild?: boolean
 +  operation?: OperationNode
+   needsKey?: boolean
  }
  
  export interface IREffect {
index 525fa323d3a569a9c557d05f3aa12d7ece2394b5,9a65e6f1bb4730d7402c5a30f3038940d55bdf3d..df8fb6a5a6a3c2157b4d230105debac2a2ba3185
@@@ -236,15 -253,12 +253,19 @@@ function createSlotBlock
  ): [SlotBlockIRNode, () => void] {
    const block: SlotBlockIRNode = newBlock(slotNode)
    block.props = dir && dir.exp
+   if (key) {
+     block.key = key
+     block.dynamic.needsKey = true
+   }
    const exitBlock = context.enterBlock(block)
 -  return [block, exitBlock]
 +  context.inSlot = true
 +  return [
 +    block,
 +    () => {
 +      context.inSlot = false
 +      exitBlock()
 +    },
 +  ]
  }
  
  function isNonWhitespaceContent(node: TemplateChildNode): boolean {
index 097c515b1ffb2c2b8759e375ba4f57fbb27fcf00,9ea669ca34254db8780c51fae9cc1b610530c369..7f5450d4ecb0c40ab7a63226aa4b95668a78158a
@@@ -187,7 -192,10 +186,11 @@@ export interface VaporInteropInterface 
    unmount(vnode: VNode, doRemove?: boolean): void
    move(vnode: VNode, container: any, anchor: any): void
    slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void
+   setTransitionHooks(
+     component: ComponentInternalInstance,
+     transition: TransitionHooks,
+   ): void
 +  hydrate(node: Node, fn: () => void): void
  
    vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
    vdomUnmount: UnmountComponentFn
index f921eb0a2cf8101695612f578f9d26e8cac54f3b,4eddabcff270e5a679344988d37e011224dc022f..889ab132b6481e1d8c374f34e0ef41405ae8711b
@@@ -557,7 -562,7 +562,11 @@@ export { startMeasure, endMeasure } fro
   * @internal
   */
  export { initFeatureFlags } from './featureFlags'
+ /**
+  * @internal
+  */
+ export { performTransitionEnter, performTransitionLeave } from './renderer'
 +/**
 + * @internal
 + */
 +export { ensureVaporSlotFallback } from './helpers/renderSlot'
index 845e1d5f24b4853bb8c7269a8e4b61fab6b3e9e8,744338b3f45a23e630bae94dc94c03726c02b924..2c97387b83e9b06d3d2464f3bd717cd3e8e40c6b
@@@ -2116,18 -2116,15 +2117,21 @@@ function baseCreateRenderer
        transition
      if (needTransition) {
        if (moveType === MoveType.ENTER) {
-         transition!.beforeEnter(el!)
-         hostInsert(el!, container, anchor)
-         queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
+         performTransitionEnter(
+           el!,
+           transition,
+           () => hostInsert(el!, container, anchor),
+           parentSuspense,
+         )
        } else {
          const { leave, delayLeave, afterLeave } = transition!
 -        const remove = () => hostInsert(el!, container, anchor)
 +        const remove = () => {
 +          if (vnode.ctx!.isUnmounted) {
 +            hostRemove(el!)
 +          } else {
 +            hostInsert(el!, container, anchor)
 +          }
 +        }
          const performLeave = () => {
            leave(el!, () => {
              remove()
Simple merge
index 7ae689a0aee74ff920ccd699b58bcaa27639032b,c061c8224cdf27e8c610882d4243956d2af36f7b..9bd4993f8372a61f85ee838957d5e3915e3b2631
@@@ -1,6 -1,6 +1,6 @@@
- import { resolveDynamicComponent } from '@vue/runtime-dom'
+ import { currentInstance, resolveDynamicComponent } from '@vue/runtime-dom'
 -import { DynamicFragment, type VaporFragment } from './block'
 +import { DynamicFragment, type VaporFragment, insert } from './block'
- import { createComponentWithFallback } from './component'
+ import { createComponentWithFallback, emptyContext } from './component'
  import { renderEffect } from './renderEffect'
  import type { RawProps } from './componentProps'
  import type { RawSlots } from './componentSlots'
@@@ -18,16 -11,13 +18,18 @@@ export function createDynamicComponent
    rawSlots?: RawSlots | null,
    isSingleRoot?: boolean,
  ): VaporFragment {
 -  const frag = __DEV__
 -    ? new DynamicFragment('dynamic-component')
 -    : new DynamicFragment()
 +  const _insertionParent = insertionParent
 +  const _insertionAnchor = insertionAnchor
 +  if (!isHydrating) resetInsertionState()
 +
 +  const frag =
 +    isHydrating || __DEV__
 +      ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
 +      : new DynamicFragment()
    renderEffect(() => {
      const value = getter()
+     const appContext =
+       (currentInstance && currentInstance.appContext) || emptyContext
      frag.update(
        () =>
          createComponentWithFallback(
index 7138d01a6af5e352c226ceb1c760d9ca79494175,92bfe562586d0e465d5fca49f3b897e4dbc83124..7bde89fbb13d909f748cd16ea85cf5e321adf458
@@@ -28,17 -22,7 +28,18 @@@ import { currentInstance, isVaporCompon
  import type { DynamicSlot } from './componentSlots'
  import { renderEffect } from './renderEffect'
  import { VaporVForFlags } from '../../shared/src/vaporFlags'
+ import { applyTransitionHooks } from './components/Transition'
 +import {
 +  currentHydrationNode,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +} from './dom/hydration'
 +import {
 +  insertionAnchor,
 +  insertionParent,
 +  resetInsertionState,
 +} from './insertionState'
  
  class ForBlock extends VaporFragment {
    scope: EffectScope | undefined
index f0dbe7ab6c571a115ae4522269366a90ed865737,6c882badcd5eef42e517a2ef30c86b5e1d6327c3..c788ce88559570660879c05938bcbabe28dcc280
@@@ -8,30 -7,53 +8,61 @@@ import 
  } from './component'
  import { createComment, createTextNode } from './dom/node'
  import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
 -import { isHydrating } from './dom/hydration'
 +import {
 +  currentHydrationNode,
 +  isComment,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +} from './dom/hydration'
 +import { queuePostFlushCb } from '@vue/runtime-dom'
+ import {
+   type TransitionHooks,
+   type TransitionProps,
+   type TransitionState,
+   performTransitionEnter,
+   performTransitionLeave,
+ } from '@vue/runtime-dom'
+ import {
+   applyTransitionHooks,
+   applyTransitionLeaveHooks,
+ } from './components/Transition'
+ export interface TransitionOptions {
+   $key?: any
+   $transition?: VaporTransitionHooks
+ }
  
- export type Block =
-   | Node
-   | VaporFragment
-   | DynamicFragment
-   | VaporComponentInstance
-   | Block[]
+ export interface VaporTransitionHooks extends TransitionHooks {
+   state: TransitionState
+   props: TransitionProps
+   instance: VaporComponentInstance
+   // mark transition hooks as disabled so that it skips during
+   // inserting
+   disabled?: boolean
+ }
+ export type TransitionBlock =
+   | (Node & TransitionOptions)
+   | (VaporFragment & TransitionOptions)
+   | (DynamicFragment & TransitionOptions)
+ export type Block = TransitionBlock | VaporComponentInstance | Block[]
  
  export type BlockFn = (...args: any[]) => Block
  
- export class VaporFragment {
+ export class VaporFragment implements TransitionOptions {
+   $key?: any
+   $transition?: VaporTransitionHooks | undefined
    nodes: Block
    anchor?: Node
-   insert?: (parent: ParentNode, anchor: Node | null) => void
-   remove?: (parent?: ParentNode) => void
+   insert?: (
+     parent: ParentNode,
+     anchor: Node | null,
+     transitionHooks?: TransitionHooks,
+   ) => void
+   remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void
 +  fallback?: BlockFn
  
    constructor(nodes: Block) {
      this.nodes = nodes
@@@ -155,9 -189,10 +218,9 @@@ export function insert
    } else {
      // fragment
      if (block.insert) {
 -      // TODO handle hydration for vdom interop
 -      block.insert(parent, anchor, (block as TransitionBlock).$transition)
 +      block.insert(parent, anchor)
      } else {
-       insert(block.nodes, parent, anchor)
+       insert(block.nodes, parent, anchor, parentSuspense)
      }
      if (block.anchor) insert(block.anchor, parent, anchor)
    }
index 00b9f26c1ba2a3bf6557cef3f4b620d0d9b260b9,e1e1feb9e6daaa6f67edc0e825e59c10cb5f562d..55fdcffeb04cc39e5ed4a986d8bd3ac55959171c
@@@ -66,12 -58,7 +66,13 @@@ import 
    getSlot,
  } from './componentSlots'
  import { hmrReload, hmrRerender } from './hmr'
+ import { createElement } from './dom/node'
 +import { isHydrating, locateHydrationNode } from './dom/hydration'
 +import {
 +  insertionAnchor,
 +  insertionParent,
 +  resetInsertionState,
 +} from './insertionState'
  
  export { currentInstance } from '@vue/runtime-dom'
  
@@@ -265,16 -234,12 +266,12 @@@ export function createComponent
    if (
      instance.hasFallthrough &&
      component.inheritAttrs !== false &&
 -    instance.block instanceof Element &&
      Object.keys(instance.attrs).length
    ) {
 -    renderEffect(() =>
 -      applyFallthroughProps(instance.block as Element, instance.attrs),
 -    )
 +    const el = getRootElement(instance)
 +    if (el) {
-       renderEffect(() => {
-         isApplyingFallthroughProps = true
-         setDynamicProps(el, [instance.attrs])
-         isApplyingFallthroughProps = false
-       })
++      applyFallthroughProps(el, instance.attrs)
 +    }
    }
  
    resetTracking()
  
  export let isApplyingFallthroughProps = false
  
 -  isApplyingFallthroughProps = true
 -  setDynamicProps(block as Element, [attrs])
 -  isApplyingFallthroughProps = false
+ export function applyFallthroughProps(
+   block: Block,
+   attrs: Record<string, any>,
+ ): void {
++  renderEffect(() => {
++    isApplyingFallthroughProps = true
++    setDynamicProps(block as Element, [attrs])
++    isApplyingFallthroughProps = false
++  })
+ }
  /**
   * dev only
   */
index 3f38c477a01158ea5b56bd0f5cd1457a9094cda7,26cb66c462cf6634b1b3f54c1b20881cfc557350..4c613324e12aedc0f2097901fc05f7a036c4c653
@@@ -1,10 -1,8 +1,15 @@@
 +import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
 +import {
 +  DYNAMIC_END_ANCHOR_LABEL,
 +  DYNAMIC_START_ANCHOR_LABEL,
 +  isVaporAnchors,
 +} from '@vue/shared'
 +
+ /*! #__NO_SIDE_EFFECTS__ */
+ export function createElement(tagName: string): HTMLElement {
+   return document.createElement(tagName)
+ }
  /*! #__NO_SIDE_EFFECTS__ */
  export function createTextNode(value = ''): Text {
    return document.createTextNode(value)
index b78ca4e52cfb8fa988d1de60ad7879a00f8f3566,14cace63c712007967df5882fe2376e1a7ec8438..f6cdc3ff4873304f31110f0cbbeb6da54842d7b4
@@@ -1,5 -1,9 +1,5 @@@
 -import {
 -  adoptHydrationNode,
 -  currentHydrationNode,
 -  isHydrating,
 -} from './hydration'
 +import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration'
- import { child, createTextNode } from './node'
+ import { child, createElement, createTextNode } from './node'
  
  let t: HTMLTemplateElement
  
Simple merge
index cf67e3e518135bc82b36569133fbc3c56b52e45a,3c3ae58e92c908f57a8eeeefd2bcb39c7d352e25..33680ba7f871d0d005e43cc986f419ee25fa09b1
@@@ -2,25 -2,20 +2,27 @@@ import 
    type App,
    type ComponentInternalInstance,
    type ConcreteComponent,
 +  type HydrationRenderer,
    MoveType,
    type Plugin,
 +  type RendererElement,
    type RendererInternals,
 +  type RendererNode,
    type ShallowRef,
    type Slots,
+   type TransitionHooks,
    type VNode,
    type VaporInteropInterface,
    createVNode,
    currentInstance,
 +  ensureHydrationRenderer,
    ensureRenderer,
 +  ensureVaporSlotFallback,
 +  isVNode,
    onScopeDispose,
    renderSlot,
+   setTransitionHooks as setVNodeTransitionHooks,
 +  shallowReactive,
    shallowRef,
    simpleSetCurrentInstance,
  } from '@vue/runtime-dom'
@@@ -35,24 -30,18 +37,32 @@@ import 
  } from './component'
  import {
    type Block,
 +  DynamicFragment,
    VaporFragment,
+   type VaporTransitionHooks,
    insert,
 +  isFragment,
    remove,
  } from './block'
- import { EMPTY_OBJ, extend, isArray, isFunction } from '@vue/shared'
 -import { EMPTY_OBJ, extend, isFunction, isReservedProp } from '@vue/shared'
++import {
++  EMPTY_OBJ,
++  extend,
++  isArray,
++  isFunction,
++  isReservedProp,
++} from '@vue/shared'
  import { type RawProps, rawPropsProxyHandlers } from './componentProps'
  import type { RawSlots, VaporSlot } from './componentSlots'
  import { renderEffect } from './renderEffect'
  import { createTextNode } from './dom/node'
  import { optimizePropertyLookup } from './dom/prop'
+ import { setTransitionHooks as setVaporTransitionHooks } from './components/Transition'
 +import {
 +  currentHydrationNode,
 +  isHydrating,
 +  locateHydrationNode,
 +  hydrateNode as vaporHydrateNode,
 +} from './dom/hydration'
  
  // mounting vapor components and slots in vdom
  const vaporInteropImpl: Omit<
      ))
      instance.rawPropsRef = propsRef
      instance.rawSlotsRef = slotsRef
+     if (vnode.transition) {
+       setVaporTransitionHooks(
+         instance,
+         vnode.transition as VaporTransitionHooks,
+       )
+     }
      mountComponent(instance, container, selfAnchor)
 +    vnode.el = instance.block
      simpleSetCurrentInstance(prev)
      return instance
    },
      insert(vnode.anchor as any, container, anchor)
    },
  
+   setTransitionHooks(component, hooks) {
+     setVaporTransitionHooks(component as any, hooks as VaporTransitionHooks)
+   },
++
 +  hydrate: vaporHydrateNode,
  }
  
  const vaporSlotPropsProxyHandler: ProxyHandler<
@@@ -223,33 -203,20 +252,35 @@@ function createVDOMComponent
      internals.umt(vnode.component!, null, !!parentNode)
    }
  
 -  frag.insert = (parentNode, anchor, transition) => {
 +  vnode.scopeId = parentInstance.type.__scopeId!
 +
 +  frag.insert = (parentNode, anchor) => {
+     const prev = currentInstance
+     simpleSetCurrentInstance(parentInstance)
 -    if (!isMounted) {
 -      if (transition) setVNodeTransitionHooks(vnode, transition)
 -      internals.mt(
 -        vnode,
 -        parentNode,
 -        anchor,
 -        parentInstance as any,
 -        null,
 -        undefined,
 -        false,
 -      )
 +    if (!isMounted || isHydrating) {
 +      if (isHydrating) {
 +        ;(
 +          vdomHydrateNode ||
 +          (vdomHydrateNode = ensureHydrationRenderer().hydrateNode!)
 +        )(
 +          currentHydrationNode!,
 +          vnode,
 +          parentInstance as any,
 +          null,
 +          null,
 +          false,
 +        )
 +      } else {
 +        internals.mt(
 +          vnode,
 +          parentNode,
 +          anchor,
 +          parentInstance as any,
 +          null,
 +          undefined,
 +          false,
 +        )
 +      }
        onScopeDispose(unmount, true)
        isMounted = true
      } else {
        )
      }
  
 -    frag.nodes = [vnode.el as Node]
+     simpleSetCurrentInstance(prev)
++
 +    // update the fragment nodes
 +    frag.nodes = vnode.el as Block
    }
  
    frag.remove = unmount
index 700a9a0ae3f182596119bbf6f3f1c7ba08bcd44d,700a9a0ae3f182596119bbf6f3f1c7ba08bcd44d..de399dbb82a24cde609b6175d10b93259a7d0c3d
@@@ -49,7 -49,7 +49,7 @@@ test('pipeToWebWritable', async () => 
    }
  
    const { readable, writable } = new TransformStream()
--  pipeToWebWritable(createApp(App), {}, writable)
++  pipeToWebWritable(createApp(App), {}, writable as any)
  
    const reader = readable.getReader()
    const decoder = new TextDecoder()