All files / runtime-core/src/helpers renderSlot.ts

97.22% Statements 35/36
98.48% Branches 65/66
100% Functions 4/4
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137                                                                        136x             33x     33x       33x 33x                     103x   103x 1x         1x             103x 70x   103x 103x   103x 103x   136x         136x                               1x 1x   103x 70x     102x 7x   102x           112x 110x 110x 102x       1x 101x          
import type { Data } from '../component'
import type { RawSlots, Slots } from '../componentSlots'
import {
  type ContextualRenderFn,
  currentRenderingInstance,
} from '../componentRenderContext'
import {
  Comment,
  Fragment,
  type VNode,
  type VNodeArrayChildren,
  blockStack,
  closeBlock,
  createBlock,
  createVNode,
  isVNode,
  openBlock,
} from '../vnode'
import { PatchFlags, SlotFlags, extend, isSymbol } from '@vue/shared'
import { warn } from '../warning'
import { isAsyncWrapper } from '../apiAsyncComponent'
 
/**
 * Compiler runtime helper for rendering `<slot/>`
 * @private
 */
export function renderSlot(
  slots: Slots,
  name: string,
  props: Data = {},
  // this is not a user-facing function, so the fallback is always generated by
  // the compiler and guaranteed to be a function returning an array
  fallback?: () => VNodeArrayChildren,
  noSlotted?: boolean,
  branchKey?: PropertyKey,
): VNode {
  if (
    currentRenderingInstance!.ce ||
    (currentRenderingInstance!.parent &&
      isAsyncWrapper(currentRenderingInstance!.parent) &&
      currentRenderingInstance!.parent.ce)
  ) {
    const slotProps =
      branchKey != null && props.key == null
        ? extend({}, props, { key: branchKey })
        : props
    const hasProps = Object.keys(slotProps).length > 0
    // in custom element mode, render <slot/> as actual slot outlets
    // wrap it with a fragment because in shadowRoot: false mode the slot
    // element gets replaced by injected content
    if (name !== 'default') slotProps.name = name
    return (
      openBlock(),
      createBlock(
        Fragment,
        null,
        [createVNode('slot', slotProps, fallback && fallback())],
        hasProps ? PatchFlags.BAIL : PatchFlags.STABLE_FRAGMENT,
      )
    )
  }
 
  let slot = slots[name]
 
  if (__DEV__ && slot && slot.length > 1) {
    warn(
      `SSR-optimized slot function detected in a non-SSR-optimized render ` +
        `function. You need to mark this component with $dynamic-slots in the ` +
        `parent template.`,
    )
    slot = () => []
  }
 
  // a compiled slot disables block tracking by default to avoid manual
  // invocation interfering with template-based block tracking, but in
  // `renderSlot` we can be sure that it's template-based so we can force
  // enable it.
  if (slot && (slot as ContextualRenderFn)._c) {
    ;(slot as ContextualRenderFn)._d = false
  }
  const prevStackSize = blockStack.length
  openBlock()
  let rendered: VNode
  try {
    const validSlotContent = slot && ensureValidVNode(slot(props))
    const slotKey =
      props.key ||
      branchKey ||
      // slot content array of a dynamic conditional slot may have a branch
      // key attached in the `createSlots` helper, respect that
      (validSlotContent && (validSlotContent as any).key)
    rendered = createBlock(
      Fragment,
      {
        key:
          (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) +
          // #7256 force differentiate fallback content from actual content
          (!validSlotContent && fallback ? '_fb' : ''),
      },
      validSlotContent || (fallback ? fallback() : []),
      validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
        ? PatchFlags.STABLE_FRAGMENT
        : PatchFlags.BAIL,
    )
  } catch (err) {
    // close blocks left dangling when the slot throws mid-block
    // they would otherwise retain every vnode created afterwards (#15070)
    for (let i = blockStack.length; i > prevStackSize; i--) closeBlock()
    throw err
  } finally {
    if (slot && (slot as ContextualRenderFn)._c) {
      ;(slot as ContextualRenderFn)._d = true
    }
  }
  if (!noSlotted && rendered.scopeId) {
    rendered.slotScopeIds = [rendered.scopeId + '-s']
  }
  return rendered
}
 
export function ensureValidVNode(
  vnodes: VNodeArrayChildren,
): VNodeArrayChildren | null {
  return vnodes.some(child => {
    Iif (!isVNode(child)) return true
    if (child.type === Comment) return false
    if (
      child.type === Fragment &&
      !ensureValidVNode(child.children as VNodeArrayChildren)
    )
      return false
    return true
  })
    ? vnodes
    : null
}