fix(compiler-ssr): fix hydration mismatch for conditional slot in transition

close #10743
This commit is contained in:
Evan You 2024-04-22 22:23:09 +08:00
parent c8e87a1c90
commit f12c81efca
No known key found for this signature in database
GPG Key ID: B9D421896CA450FB
3 changed files with 44 additions and 17 deletions

View File

@ -102,6 +102,9 @@ export interface TransformContext
vOnce: number vOnce: number
} }
parent: ParentNode | null parent: ParentNode | null
// we could use a stack but in practice we've only ever needed two layers up
// so this is more efficient
grandParent: ParentNode | null
childIndex: number childIndex: number
currentNode: RootNode | TemplateChildNode | null currentNode: RootNode | TemplateChildNode | null
inVOnce: boolean inVOnce: boolean
@ -193,6 +196,7 @@ export function createTransformContext(
vOnce: 0, vOnce: 0,
}, },
parent: null, parent: null,
grandParent: null,
currentNode: root, currentNode: root,
childIndex: 0, childIndex: 0,
inVOnce: false, inVOnce: false,
@ -401,6 +405,7 @@ export function traverseChildren(
for (; i < parent.children.length; i++) { for (; i < parent.children.length; i++) {
const child = parent.children[i] const child = parent.children[i]
if (isString(child)) continue if (isString(child)) continue
context.grandParent = context.parent
context.parent = parent context.parent = parent
context.childIndex = i context.childIndex = i
context.onNodeRemoved = nodeRemoved context.onNodeRemoved = nodeRemoved

View File

@ -143,4 +143,20 @@ describe('ssr: <slot>', () => {
}" }"
`) `)
}) })
test('with v-if inside transition', () => {
const { code } = compile(`<transition><slot v-if="true"/></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) {
if (true) {
_ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
} else {
_push(\`<!---->\`)
}
}"
`)
})
}) })

View File

@ -40,18 +40,23 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
// #3989, #9933 // #3989, #9933
// check if this is a single slot inside a transition wrapper - since // check if this is a single slot inside a transition wrapper - since
// transition/transition-group will unwrap the slot fragment into vnode(s) at runtime, // transition/transition-group will unwrap the slot fragment into vnode(s)
// we need to avoid rendering the slot as a fragment. // at runtime, we need to avoid rendering the slot as a fragment.
const parent = context.parent let parent = context.parent!
if (parent) {
const children = parent.children
// #10743 <slot v-if> in <Transition>
if (parent.type === NodeTypes.IF_BRANCH) {
parent = context.grandParent!
}
let componentType let componentType
if ( if (
parent &&
parent.type === NodeTypes.ELEMENT && parent.type === NodeTypes.ELEMENT &&
parent.tagType === ElementTypes.COMPONENT && parent.tagType === ElementTypes.COMPONENT &&
((componentType = resolveComponentType(parent, context, true)) === ((componentType = resolveComponentType(parent, context, true)) ===
TRANSITION || TRANSITION ||
componentType === TRANSITION_GROUP) && componentType === TRANSITION_GROUP) &&
parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1 children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
) { ) {
method = SSR_RENDER_SLOT_INNER method = SSR_RENDER_SLOT_INNER
if (!(context.scopeId && context.slotted !== false)) { if (!(context.scopeId && context.slotted !== false)) {
@ -59,6 +64,7 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
} }
args.push('true') args.push('true')
} }
}
node.ssrCodegenNode = createCallExpression(context.helper(method), args) node.ssrCodegenNode = createCallExpression(context.helper(method), args)
} }