fix(ssr): fix hydration error for slot outlet inside transition
fix #3989
This commit is contained in:
		
							parent
							
								
									da49c863a2
								
							
						
					
					
						commit
						9309b044bd
					
				@ -1,4 +1,5 @@
 | 
			
		||||
import { compile } from '../src'
 | 
			
		||||
import { ssrHelpers, SSR_RENDER_SLOT_INNER } from '../src/runtimeHelpers'
 | 
			
		||||
 | 
			
		||||
describe('ssr: <slot>', () => {
 | 
			
		||||
  test('basic', () => {
 | 
			
		||||
@ -114,4 +115,16 @@ describe('ssr: <slot>', () => {
 | 
			
		||||
      }"
 | 
			
		||||
    `)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('inside transition', () => {
 | 
			
		||||
    const { code } = compile(`<transition><slot/></transition>`)
 | 
			
		||||
    expect(code).toMatch(ssrHelpers[SSR_RENDER_SLOT_INNER])
 | 
			
		||||
    expect(code).toMatchInlineSnapshot(`
 | 
			
		||||
      "const { ssrRenderSlotInner: _ssrRenderSlotInner } = require(\\"vue/server-renderer\\")
 | 
			
		||||
 | 
			
		||||
      return function ssrRender(_ctx, _push, _parent, _attrs) {
 | 
			
		||||
        _ssrRenderSlotInner(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
 | 
			
		||||
      }"
 | 
			
		||||
    `)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ export const SSR_INTERPOLATE = Symbol(`ssrInterpolate`)
 | 
			
		||||
export const SSR_RENDER_VNODE = Symbol(`ssrRenderVNode`)
 | 
			
		||||
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
 | 
			
		||||
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
 | 
			
		||||
export const SSR_RENDER_SLOT_INNER = Symbol(`ssrRenderSlotInner`)
 | 
			
		||||
export const SSR_RENDER_CLASS = Symbol(`ssrRenderClass`)
 | 
			
		||||
export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`)
 | 
			
		||||
export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`)
 | 
			
		||||
@ -24,6 +25,7 @@ export const ssrHelpers = {
 | 
			
		||||
  [SSR_RENDER_VNODE]: `ssrRenderVNode`,
 | 
			
		||||
  [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
 | 
			
		||||
  [SSR_RENDER_SLOT]: `ssrRenderSlot`,
 | 
			
		||||
  [SSR_RENDER_SLOT_INNER]: `ssrRenderSlotInner`,
 | 
			
		||||
  [SSR_RENDER_CLASS]: `ssrRenderClass`,
 | 
			
		||||
  [SSR_RENDER_STYLE]: `ssrRenderStyle`,
 | 
			
		||||
  [SSR_RENDER_ATTRS]: `ssrRenderAttrs`,
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,13 @@ import {
 | 
			
		||||
  processSlotOutlet,
 | 
			
		||||
  createCallExpression,
 | 
			
		||||
  SlotOutletNode,
 | 
			
		||||
  createFunctionExpression
 | 
			
		||||
  createFunctionExpression,
 | 
			
		||||
  NodeTypes,
 | 
			
		||||
  ElementTypes,
 | 
			
		||||
  resolveComponentType,
 | 
			
		||||
  TRANSITION
 | 
			
		||||
} from '@vue/compiler-dom'
 | 
			
		||||
import { SSR_RENDER_SLOT } from '../runtimeHelpers'
 | 
			
		||||
import { SSR_RENDER_SLOT, SSR_RENDER_SLOT_INNER } from '../runtimeHelpers'
 | 
			
		||||
import {
 | 
			
		||||
  SSRTransformContext,
 | 
			
		||||
  processChildrenAsStatement
 | 
			
		||||
@ -31,10 +35,24 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
 | 
			
		||||
      args.push(`"${context.scopeId}-s"`)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node.ssrCodegenNode = createCallExpression(
 | 
			
		||||
      context.helper(SSR_RENDER_SLOT),
 | 
			
		||||
      args
 | 
			
		||||
    )
 | 
			
		||||
    let method = SSR_RENDER_SLOT
 | 
			
		||||
 | 
			
		||||
    // #3989
 | 
			
		||||
    // check if this is a single slot inside a transition wrapper - since
 | 
			
		||||
    // transition will unwrap the slot fragment into a single vnode at runtime,
 | 
			
		||||
    // we need to avoid rendering the slot as a fragment.
 | 
			
		||||
    const parent = context.parent
 | 
			
		||||
    if (
 | 
			
		||||
      parent &&
 | 
			
		||||
      parent.type === NodeTypes.ELEMENT &&
 | 
			
		||||
      parent.tagType === ElementTypes.COMPONENT &&
 | 
			
		||||
      resolveComponentType(parent, context, true) === TRANSITION &&
 | 
			
		||||
      parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
 | 
			
		||||
    ) {
 | 
			
		||||
      method = SSR_RENDER_SLOT_INNER
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node.ssrCodegenNode = createCallExpression(context.helper(method), args)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -113,7 +113,7 @@ export function createHydrationFunctions(
 | 
			
		||||
          nextNode = onMismatch()
 | 
			
		||||
        } else {
 | 
			
		||||
          if ((node as Text).data !== vnode.children) {
 | 
			
		||||
            hasMismatch = true
 | 
			
		||||
            hasMismatch = true; debugger
 | 
			
		||||
            __DEV__ &&
 | 
			
		||||
              warn(
 | 
			
		||||
                `Hydration text mismatch:` +
 | 
			
		||||
@ -351,7 +351,7 @@ export function createHydrationFunctions(
 | 
			
		||||
        )
 | 
			
		||||
        let hasWarned = false
 | 
			
		||||
        while (next) {
 | 
			
		||||
          hasMismatch = true
 | 
			
		||||
          hasMismatch = true; debugger
 | 
			
		||||
          if (__DEV__ && !hasWarned) {
 | 
			
		||||
            warn(
 | 
			
		||||
              `Hydration children mismatch in <${vnode.type as string}>: ` +
 | 
			
		||||
@ -366,7 +366,7 @@ export function createHydrationFunctions(
 | 
			
		||||
        }
 | 
			
		||||
      } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
 | 
			
		||||
        if (el.textContent !== vnode.children) {
 | 
			
		||||
          hasMismatch = true
 | 
			
		||||
          hasMismatch = true; debugger
 | 
			
		||||
          __DEV__ &&
 | 
			
		||||
            warn(
 | 
			
		||||
              `Hydration text content mismatch in <${
 | 
			
		||||
@ -411,7 +411,7 @@ export function createHydrationFunctions(
 | 
			
		||||
      } else if (vnode.type === Text && !vnode.children) {
 | 
			
		||||
        continue
 | 
			
		||||
      } else {
 | 
			
		||||
        hasMismatch = true
 | 
			
		||||
        hasMismatch = true; debugger
 | 
			
		||||
        if (__DEV__ && !hasWarned) {
 | 
			
		||||
          warn(
 | 
			
		||||
            `Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
 | 
			
		||||
@ -465,7 +465,7 @@ export function createHydrationFunctions(
 | 
			
		||||
    } else {
 | 
			
		||||
      // fragment didn't hydrate successfully, since we didn't get a end anchor
 | 
			
		||||
      // back. This should have led to node/children mismatch warnings.
 | 
			
		||||
      hasMismatch = true
 | 
			
		||||
      hasMismatch = true; debugger
 | 
			
		||||
      // since the anchor is missing, we need to create one and insert it
 | 
			
		||||
      insert((vnode.anchor = createComment(`]`)), container, next)
 | 
			
		||||
      return next
 | 
			
		||||
@ -480,7 +480,7 @@ export function createHydrationFunctions(
 | 
			
		||||
    slotScopeIds: string[] | null,
 | 
			
		||||
    isFragment: boolean
 | 
			
		||||
  ): Node | null => {
 | 
			
		||||
    hasMismatch = true
 | 
			
		||||
    hasMismatch = true; debugger
 | 
			
		||||
    __DEV__ &&
 | 
			
		||||
      warn(
 | 
			
		||||
        `Hydration node mismatch:\n- Client vnode:`,
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,27 @@ export function ssrRenderSlot(
 | 
			
		||||
) {
 | 
			
		||||
  // template-compiled slots are always rendered as fragments
 | 
			
		||||
  push(`<!--[-->`)
 | 
			
		||||
  ssrRenderSlotInner(
 | 
			
		||||
    slots,
 | 
			
		||||
    slotName,
 | 
			
		||||
    slotProps,
 | 
			
		||||
    fallbackRenderFn,
 | 
			
		||||
    push,
 | 
			
		||||
    parentComponent,
 | 
			
		||||
    slotScopeId
 | 
			
		||||
  )
 | 
			
		||||
  push(`<!--]-->`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function ssrRenderSlotInner(
 | 
			
		||||
  slots: Slots | SSRSlots,
 | 
			
		||||
  slotName: string,
 | 
			
		||||
  slotProps: Props,
 | 
			
		||||
  fallbackRenderFn: (() => void) | null,
 | 
			
		||||
  push: PushFn,
 | 
			
		||||
  parentComponent: ComponentInternalInstance,
 | 
			
		||||
  slotScopeId?: string
 | 
			
		||||
) {
 | 
			
		||||
  const slotFn = slots[slotName]
 | 
			
		||||
  if (slotFn) {
 | 
			
		||||
    const slotBuffer: SSRBufferItem[] = []
 | 
			
		||||
@ -59,7 +80,6 @@ export function ssrRenderSlot(
 | 
			
		||||
  } else if (fallbackRenderFn) {
 | 
			
		||||
    fallbackRenderFn()
 | 
			
		||||
  }
 | 
			
		||||
  push(`<!--]-->`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const commentRE = /^<!--.*-->$/
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ export {
 | 
			
		||||
// internal runtime helpers
 | 
			
		||||
export { renderVNode as ssrRenderVNode } from './render'
 | 
			
		||||
export { ssrRenderComponent } from './helpers/ssrRenderComponent'
 | 
			
		||||
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
 | 
			
		||||
export { ssrRenderSlot, ssrRenderSlotInner } from './helpers/ssrRenderSlot'
 | 
			
		||||
export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
 | 
			
		||||
export {
 | 
			
		||||
  ssrRenderClass,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user