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 { compile } from '../src'
 | 
				
			||||||
 | 
					import { ssrHelpers, SSR_RENDER_SLOT_INNER } from '../src/runtimeHelpers'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('ssr: <slot>', () => {
 | 
					describe('ssr: <slot>', () => {
 | 
				
			||||||
  test('basic', () => {
 | 
					  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_VNODE = Symbol(`ssrRenderVNode`)
 | 
				
			||||||
export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
 | 
					export const SSR_RENDER_COMPONENT = Symbol(`ssrRenderComponent`)
 | 
				
			||||||
export const SSR_RENDER_SLOT = Symbol(`ssrRenderSlot`)
 | 
					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_CLASS = Symbol(`ssrRenderClass`)
 | 
				
			||||||
export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`)
 | 
					export const SSR_RENDER_STYLE = Symbol(`ssrRenderStyle`)
 | 
				
			||||||
export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`)
 | 
					export const SSR_RENDER_ATTRS = Symbol(`ssrRenderAttrs`)
 | 
				
			||||||
@ -24,6 +25,7 @@ export const ssrHelpers = {
 | 
				
			|||||||
  [SSR_RENDER_VNODE]: `ssrRenderVNode`,
 | 
					  [SSR_RENDER_VNODE]: `ssrRenderVNode`,
 | 
				
			||||||
  [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
 | 
					  [SSR_RENDER_COMPONENT]: `ssrRenderComponent`,
 | 
				
			||||||
  [SSR_RENDER_SLOT]: `ssrRenderSlot`,
 | 
					  [SSR_RENDER_SLOT]: `ssrRenderSlot`,
 | 
				
			||||||
 | 
					  [SSR_RENDER_SLOT_INNER]: `ssrRenderSlotInner`,
 | 
				
			||||||
  [SSR_RENDER_CLASS]: `ssrRenderClass`,
 | 
					  [SSR_RENDER_CLASS]: `ssrRenderClass`,
 | 
				
			||||||
  [SSR_RENDER_STYLE]: `ssrRenderStyle`,
 | 
					  [SSR_RENDER_STYLE]: `ssrRenderStyle`,
 | 
				
			||||||
  [SSR_RENDER_ATTRS]: `ssrRenderAttrs`,
 | 
					  [SSR_RENDER_ATTRS]: `ssrRenderAttrs`,
 | 
				
			||||||
 | 
				
			|||||||
@ -4,9 +4,13 @@ import {
 | 
				
			|||||||
  processSlotOutlet,
 | 
					  processSlotOutlet,
 | 
				
			||||||
  createCallExpression,
 | 
					  createCallExpression,
 | 
				
			||||||
  SlotOutletNode,
 | 
					  SlotOutletNode,
 | 
				
			||||||
  createFunctionExpression
 | 
					  createFunctionExpression,
 | 
				
			||||||
 | 
					  NodeTypes,
 | 
				
			||||||
 | 
					  ElementTypes,
 | 
				
			||||||
 | 
					  resolveComponentType,
 | 
				
			||||||
 | 
					  TRANSITION
 | 
				
			||||||
} from '@vue/compiler-dom'
 | 
					} from '@vue/compiler-dom'
 | 
				
			||||||
import { SSR_RENDER_SLOT } from '../runtimeHelpers'
 | 
					import { SSR_RENDER_SLOT, SSR_RENDER_SLOT_INNER } from '../runtimeHelpers'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  SSRTransformContext,
 | 
					  SSRTransformContext,
 | 
				
			||||||
  processChildrenAsStatement
 | 
					  processChildrenAsStatement
 | 
				
			||||||
@ -31,10 +35,24 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
 | 
				
			|||||||
      args.push(`"${context.scopeId}-s"`)
 | 
					      args.push(`"${context.scopeId}-s"`)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    node.ssrCodegenNode = createCallExpression(
 | 
					    let method = SSR_RENDER_SLOT
 | 
				
			||||||
      context.helper(SSR_RENDER_SLOT),
 | 
					
 | 
				
			||||||
      args
 | 
					    // #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()
 | 
					          nextNode = onMismatch()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          if ((node as Text).data !== vnode.children) {
 | 
					          if ((node as Text).data !== vnode.children) {
 | 
				
			||||||
            hasMismatch = true
 | 
					            hasMismatch = true; debugger
 | 
				
			||||||
            __DEV__ &&
 | 
					            __DEV__ &&
 | 
				
			||||||
              warn(
 | 
					              warn(
 | 
				
			||||||
                `Hydration text mismatch:` +
 | 
					                `Hydration text mismatch:` +
 | 
				
			||||||
@ -351,7 +351,7 @@ export function createHydrationFunctions(
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        let hasWarned = false
 | 
					        let hasWarned = false
 | 
				
			||||||
        while (next) {
 | 
					        while (next) {
 | 
				
			||||||
          hasMismatch = true
 | 
					          hasMismatch = true; debugger
 | 
				
			||||||
          if (__DEV__ && !hasWarned) {
 | 
					          if (__DEV__ && !hasWarned) {
 | 
				
			||||||
            warn(
 | 
					            warn(
 | 
				
			||||||
              `Hydration children mismatch in <${vnode.type as string}>: ` +
 | 
					              `Hydration children mismatch in <${vnode.type as string}>: ` +
 | 
				
			||||||
@ -366,7 +366,7 @@ export function createHydrationFunctions(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
 | 
					      } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
 | 
				
			||||||
        if (el.textContent !== vnode.children) {
 | 
					        if (el.textContent !== vnode.children) {
 | 
				
			||||||
          hasMismatch = true
 | 
					          hasMismatch = true; debugger
 | 
				
			||||||
          __DEV__ &&
 | 
					          __DEV__ &&
 | 
				
			||||||
            warn(
 | 
					            warn(
 | 
				
			||||||
              `Hydration text content mismatch in <${
 | 
					              `Hydration text content mismatch in <${
 | 
				
			||||||
@ -411,7 +411,7 @@ export function createHydrationFunctions(
 | 
				
			|||||||
      } else if (vnode.type === Text && !vnode.children) {
 | 
					      } else if (vnode.type === Text && !vnode.children) {
 | 
				
			||||||
        continue
 | 
					        continue
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        hasMismatch = true
 | 
					        hasMismatch = true; debugger
 | 
				
			||||||
        if (__DEV__ && !hasWarned) {
 | 
					        if (__DEV__ && !hasWarned) {
 | 
				
			||||||
          warn(
 | 
					          warn(
 | 
				
			||||||
            `Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
 | 
					            `Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
 | 
				
			||||||
@ -465,7 +465,7 @@ export function createHydrationFunctions(
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // fragment didn't hydrate successfully, since we didn't get a end anchor
 | 
					      // fragment didn't hydrate successfully, since we didn't get a end anchor
 | 
				
			||||||
      // back. This should have led to node/children mismatch warnings.
 | 
					      // 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
 | 
					      // since the anchor is missing, we need to create one and insert it
 | 
				
			||||||
      insert((vnode.anchor = createComment(`]`)), container, next)
 | 
					      insert((vnode.anchor = createComment(`]`)), container, next)
 | 
				
			||||||
      return next
 | 
					      return next
 | 
				
			||||||
@ -480,7 +480,7 @@ export function createHydrationFunctions(
 | 
				
			|||||||
    slotScopeIds: string[] | null,
 | 
					    slotScopeIds: string[] | null,
 | 
				
			||||||
    isFragment: boolean
 | 
					    isFragment: boolean
 | 
				
			||||||
  ): Node | null => {
 | 
					  ): Node | null => {
 | 
				
			||||||
    hasMismatch = true
 | 
					    hasMismatch = true; debugger
 | 
				
			||||||
    __DEV__ &&
 | 
					    __DEV__ &&
 | 
				
			||||||
      warn(
 | 
					      warn(
 | 
				
			||||||
        `Hydration node mismatch:\n- Client vnode:`,
 | 
					        `Hydration node mismatch:\n- Client vnode:`,
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,27 @@ export function ssrRenderSlot(
 | 
				
			|||||||
) {
 | 
					) {
 | 
				
			||||||
  // template-compiled slots are always rendered as fragments
 | 
					  // template-compiled slots are always rendered as fragments
 | 
				
			||||||
  push(`<!--[-->`)
 | 
					  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]
 | 
					  const slotFn = slots[slotName]
 | 
				
			||||||
  if (slotFn) {
 | 
					  if (slotFn) {
 | 
				
			||||||
    const slotBuffer: SSRBufferItem[] = []
 | 
					    const slotBuffer: SSRBufferItem[] = []
 | 
				
			||||||
@ -59,7 +80,6 @@ export function ssrRenderSlot(
 | 
				
			|||||||
  } else if (fallbackRenderFn) {
 | 
					  } else if (fallbackRenderFn) {
 | 
				
			||||||
    fallbackRenderFn()
 | 
					    fallbackRenderFn()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  push(`<!--]-->`)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const commentRE = /^<!--.*-->$/
 | 
					const commentRE = /^<!--.*-->$/
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ export {
 | 
				
			|||||||
// internal runtime helpers
 | 
					// internal runtime helpers
 | 
				
			||||||
export { renderVNode as ssrRenderVNode } from './render'
 | 
					export { renderVNode as ssrRenderVNode } from './render'
 | 
				
			||||||
export { ssrRenderComponent } from './helpers/ssrRenderComponent'
 | 
					export { ssrRenderComponent } from './helpers/ssrRenderComponent'
 | 
				
			||||||
export { ssrRenderSlot } from './helpers/ssrRenderSlot'
 | 
					export { ssrRenderSlot, ssrRenderSlotInner } from './helpers/ssrRenderSlot'
 | 
				
			||||||
export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
 | 
					export { ssrRenderTeleport } from './helpers/ssrRenderTeleport'
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
  ssrRenderClass,
 | 
					  ssrRenderClass,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user