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