]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'edison/feat/fowardedSlots' into edison/testVapor
authordaiwei <daiwei521@126.com>
Tue, 29 Jul 2025 06:25:18 +0000 (14:25 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 29 Jul 2025 06:25:18 +0000 (14:25 +0800)
1  2 
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/fragment.ts
packages/runtime-vapor/src/vdomInterop.ts

index 5bde0790c8f446df4bed2a15930a2e7998752e01,0f032e685e901dc1cc83236d4ef2440dcf9bb4d5..643a73b3a25923a510b12822e5d3f86ec4e0f241
@@@ -11,22 -11,22 +11,22 @@@ import 
    toReadonly,
    watch,
  } from '@vue/reactivity'
 -import { isArray, isObject, isString } from '@vue/shared'
 +import { FOR_ANCHOR_LABEL, isArray, isObject, isString } from '@vue/shared'
  import { createComment, createTextNode } from './dom/node'
- import { type Block, insert, remove as removeBlock } from './block'
 -import {
 -  type Block,
 -  ForFragment,
 -  VaporFragment,
 -  insert,
 -  remove,
 -  remove as removeBlock,
 -} from './block'
++import { type Block, insert, remove, remove as removeBlock } from './block'
  import { warn } from '@vue/runtime-dom'
  import { currentInstance, isVaporComponent } from './component'
  import type { DynamicSlot } from './componentSlots'
  import { renderEffect } from './renderEffect'
  import { VaporVForFlags } from '../../shared/src/vaporFlags'
 -import { isHydrating, locateHydrationNode } from './dom/hydration'
 +import {
 +  currentHydrationNode,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +  updateNextChildToHydrate,
 +} from './dom/hydration'
- import { VaporFragment } from './fragment'
++import { ForFragment, VaporFragment } from './fragment'
  import {
    insertionAnchor,
    insertionParent,
@@@ -95,21 -94,9 +95,21 @@@ export const createFor = 
    let parent: ParentNode | undefined | null
    // useSelector only
    let currentKey: any
 -  // TODO handle this in hydration
 -  const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
 +  let parentAnchor: Node
 +  if (isHydrating) {
 +    parentAnchor = locateVaporFragmentAnchor(
 +      currentHydrationNode!,
 +      FOR_ANCHOR_LABEL,
 +    )!
 +    if (__DEV__ && !parentAnchor) {
 +      // this should not happen
 +      throw new Error(`v-for fragment anchor node was not found.`)
 +    }
 +  } else {
 +    parentAnchor = __DEV__ ? createComment('for') : createTextNode()
 +  }
 +
-   const frag = new VaporFragment(oldBlocks)
+   const frag = new ForFragment(oldBlocks)
    const instance = currentInstance!
    const canUseFastRemove = !!(flags & VaporVForFlags.FAST_REMOVE)
    const isComponent = !!(flags & VaporVForFlags.IS_COMPONENT)
Simple merge
index 2831dd5fc686a4f9ffdcd09a4d628f41df4f9785,78d823b367424ff1797865ef626b882cc04e79a2..3a965c2c06b491cd9cbd0ee7e07afa97a6e44e6d
@@@ -16,8 -9,7 +16,8 @@@ import 
    insertionParent,
    resetInsertionState,
  } from './insertionState'
 -import { isHydrating, locateHydrationNode } from './dom/hydration'
 +import { isHydrating } from './dom/hydration'
- import { DynamicFragment, type VaporFragment, isFragment } from './fragment'
++import { DynamicFragment } from './fragment'
  
  export type RawSlots = Record<string, VaporSlot> & {
    $?: DynamicSlotSource[]
@@@ -204,24 -160,3 +177,17 @@@ export function createSlot
  
    return fragment
  }
-   return block instanceof DynamicFragment && !!block.forwarded
 +
 +function isForwardedSlot(block: Block): block is DynamicFragment {
- function ensureVaporSlotFallback(
-   block: VaporFragment,
-   fallback?: VaporSlot,
- ): void {
-   if (block.insert && !block.fallback && fallback) {
-     block.fallback = fallback
-   }
- }
++  return (
++    block instanceof DynamicFragment && !!(block as DynamicFragment).forwarded
++  )
 +}
 +
 +function hasForwardedSlot(block: Block): block is DynamicFragment {
 +  if (isArray(block)) {
 +    return block.some(isForwardedSlot)
 +  } else {
 +    return isForwardedSlot(block)
 +  }
 +}
index ce74e07f74ff8512ff376a9de1ad59bd5aa7b68d,0000000000000000000000000000000000000000..9f51fac3827bd67f0b535a16a6947cf91703522d
mode 100644,000000..100644
--- /dev/null
@@@ -1,150 -1,0 +1,204 @@@
- export class VaporFragment implements TransitionOptions {
 +import { EffectScope, setActiveSub } from '@vue/reactivity'
 +import { createComment, createTextNode } from './dom/node'
 +import {
 +  type Block,
 +  type BlockFn,
 +  type TransitionOptions,
 +  type VaporTransitionHooks,
 +  insert,
 +  isValidBlock,
 +  remove,
 +} from './block'
 +import type { TransitionHooks } from '@vue/runtime-dom'
 +import {
 +  currentHydrationNode,
 +  isComment,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +  setCurrentHydrationNode,
 +} from './dom/hydration'
 +import {
 +  applyTransitionHooks,
 +  applyTransitionLeaveHooks,
 +} from './components/Transition'
 +import type { VaporComponentInstance } from './component'
 +
-   nodes: Block
++export class VaporFragment<T extends Block = Block>
++  implements TransitionOptions
++{
 +  $key?: any
 +  $transition?: VaporTransitionHooks | undefined
-   constructor(nodes: Block) {
++  nodes: T
 +  anchor?: Node
 +  insert?: (
 +    parent: ParentNode,
 +    anchor: Node | null,
 +    transitionHooks?: TransitionHooks,
 +  ) => void
 +  remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void
 +  fallback?: BlockFn
 +
 +  target?: ParentNode | null
 +  targetAnchor?: Node | null
 +  getNodes?: () => Block
 +  setRef?: (comp: VaporComponentInstance) => void
 +
-     if (this.fallback && !isValidBlock(this.nodes)) {
-       parent && remove(this.nodes, parent)
-       this.nodes =
-         (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
-         []
-       parent && insert(this.nodes, parent, this.anchor)
++  constructor(nodes: T) {
 +    this.nodes = nodes
 +  }
 +}
 +
 +export class DynamicFragment extends VaporFragment {
 +  anchor!: Node
 +  scope: EffectScope | undefined
 +  current?: BlockFn
 +  fallback?: BlockFn
 +  /**
 +   * slot only
 +   * indicates forwarded slot
 +   */
 +  forwarded?: boolean
 +
 +  constructor(anchorLabel?: string) {
 +    super([])
 +    if (isHydrating) {
 +      locateHydrationNode(anchorLabel === 'slot')
 +      this.hydrate(anchorLabel!)
 +    } else {
 +      this.anchor =
 +        __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
 +    }
 +  }
 +
 +  update(render?: BlockFn, key: any = render): void {
 +    if (key === this.current) {
 +      return
 +    }
 +    this.current = key
 +
 +    const prevSub = setActiveSub()
 +    const parent = this.anchor.parentNode
 +    const transition = this.$transition
 +    const renderBranch = () => {
 +      if (render) {
 +        this.scope = new EffectScope()
 +        this.nodes = this.scope.run(render) || []
 +        if (transition) {
 +          this.$transition = applyTransitionHooks(this.nodes, transition)
 +        }
 +        if (parent) insert(this.nodes, parent, this.anchor)
 +      } else {
 +        this.scope = undefined
 +        this.nodes = []
 +      }
 +    }
 +
 +    // teardown previous branch
 +    if (this.scope) {
 +      this.scope.stop()
 +      const mode = transition && transition.mode
 +      if (mode) {
 +        applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
 +        parent && remove(this.nodes, parent)
 +        if (mode === 'out-in') {
 +          setActiveSub(prevSub)
 +          return
 +        }
 +      } else {
 +        parent && remove(this.nodes, parent)
 +      }
 +    }
 +
 +    renderBranch()
 +
++    if (this.fallback) {
++      // set fallback for nested fragments
++      const hasNestedFragment = isFragment(this.nodes)
++      if (hasNestedFragment) {
++        setFragmentFallback(this.nodes as VaporFragment, this.fallback)
++      }
++
++      const invalidFragment = findInvalidFragment(this)
++      if (invalidFragment) {
++        parent && remove(this.nodes, parent)
++        const scope = this.scope || (this.scope = new EffectScope())
++        scope.run(() => {
++          // for nested fragments, render invalid fragment's fallback
++          if (hasNestedFragment) {
++            renderFragmentFallback(invalidFragment)
++          } else {
++            this.nodes = this.fallback!() || []
++          }
++        })
++        parent && insert(this.nodes, parent, this.anchor)
++      }
 +    }
 +
 +    if (isHydrating) {
 +      setCurrentHydrationNode(this.anchor.nextSibling)
 +    }
 +    setActiveSub(prevSub)
 +  }
 +
 +  hydrate(label: string): void {
 +    // for `v-if="false"` the node will be an empty comment, use it as the anchor.
 +    // otherwise, find next sibling vapor fragment anchor
 +    if (label === 'if' && isComment(currentHydrationNode!, '')) {
 +      this.anchor = currentHydrationNode
 +    } else {
 +      let anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
 +      if (!anchor && (label === 'slot' || label === 'if')) {
 +        // fallback to fragment end anchor for ssr vdom slot
 +        anchor = locateVaporFragmentAnchor(currentHydrationNode!, ']')!
 +      }
 +      if (anchor) {
 +        this.anchor = anchor
 +      } else if (__DEV__) {
 +        // this should not happen
 +        throw new Error(`${label} fragment anchor node was not found.`)
 +      }
 +    }
 +  }
 +}
 +
++export class ForFragment extends VaporFragment<Block[]> {
++  constructor(nodes: Block[]) {
++    super(nodes)
++  }
++}
++
 +export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
 +  return val instanceof VaporFragment
 +}
++
++export function setFragmentFallback(
++  fragment: VaporFragment,
++  fallback: BlockFn,
++): void {
++  // stop recursion if fragment has its own fallback
++  if (fragment.fallback) return
++
++  fragment.fallback = fallback
++  if (isFragment(fragment.nodes)) {
++    setFragmentFallback(fragment.nodes, fallback)
++  }
++}
++
++function renderFragmentFallback(fragment: VaporFragment): void {
++  if (fragment instanceof ForFragment) {
++    fragment.nodes[0] = [fragment.fallback!() || []] as Block[]
++  } else if (fragment instanceof DynamicFragment) {
++    fragment.update(fragment.fallback)
++  } else {
++    // vdom slots
++  }
++}
++
++function findInvalidFragment(fragment: VaporFragment): VaporFragment | null {
++  if (isValidBlock(fragment.nodes)) return null
++
++  return isFragment(fragment.nodes)
++    ? findInvalidFragment(fragment.nodes) || fragment
++    : fragment
++}
index e02b3d0cfefadf5cc4c5fde16650a2270d7b397f,eb579a23beaff3dcb187c88544ba0a2235e2865f..ff1da72f2b23fe3f9383abe8fc3d4eef84389ed4
@@@ -48,18 -45,8 +48,18 @@@ import 
  import { type RawProps, rawPropsProxyHandlers } from './componentProps'
  import type { RawSlots, VaporSlot } from './componentSlots'
  import { renderEffect } from './renderEffect'
 -import { createTextNode } from './dom/node'
 +import { __next, createTextNode } from './dom/node'
  import { optimizePropertyLookup } from './dom/prop'
- import { DynamicFragment, VaporFragment, isFragment } from './fragment'
 +import { setTransitionHooks as setVaporTransitionHooks } from './components/Transition'
 +import {
 +  currentHydrationNode,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +  setCurrentHydrationNode,
 +  hydrateNode as vaporHydrateNode,
 +} from './dom/hydration'
++import { VaporFragment, isFragment, setFragmentFallback } from './fragment'
  
  export const interopKey: unique symbol = Symbol(`interop`)
  
@@@ -226,12 -171,11 +216,12 @@@ function createVDOMComponent
    component: ConcreteComponent,
    rawProps?: LooseRawProps | null,
    rawSlots?: LooseRawSlots | null,
 +  scopeId?: string,
  ): VaporFragment {
--  const frag = new VaporFragment([])
++  const frag = new VaporFragment([] as Block[])
    const vnode = createVNode(
      component,
 -    rawProps && new Proxy(rawProps, rawPropsProxyHandlers),
 +    rawProps && extend({}, new Proxy(rawProps, rawPropsProxyHandlers)),
    )
    const wrapper = new VaporComponentInstance(
      { props: component.props },