]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-ssr): fix hydration mismatch for conditional slot in transition
authorEvan You <yyx990803@gmail.com>
Mon, 22 Apr 2024 14:23:09 +0000 (22:23 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 22 Apr 2024 14:23:09 +0000 (22:23 +0800)
close #10743

packages/compiler-core/src/transform.ts
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts

index 322d0e17638693a8d88a07386c9f710a501216b9..69821f7f879f30263cc6e216b26cf50aa27c290b 100644 (file)
@@ -102,6 +102,9 @@ export interface TransformContext
     vOnce: number
   }
   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
   currentNode: RootNode | TemplateChildNode | null
   inVOnce: boolean
@@ -193,6 +196,7 @@ export function createTransformContext(
       vOnce: 0,
     },
     parent: null,
+    grandParent: null,
     currentNode: root,
     childIndex: 0,
     inVOnce: false,
@@ -401,6 +405,7 @@ export function traverseChildren(
   for (; i < parent.children.length; i++) {
     const child = parent.children[i]
     if (isString(child)) continue
+    context.grandParent = context.parent
     context.parent = parent
     context.childIndex = i
     context.onNodeRemoved = nodeRemoved
index 655d68efbbc54fc5ee88000c6ec6bfd17e5135ff..86863cfb85f07d85d2b647fc2118d6e75f0d877a 100644 (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(\`<!---->\`)
+        }
+      }"
+    `)
+  })
 })
index ad08a23a4452dbb6ef291f439598c19f40b11fb0..f4e6dba209d8ce50a5b8ad27e5c239c640859d7f 100644 (file)
@@ -40,24 +40,30 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
 
     // #3989, #9933
     // 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,
-    // we need to avoid rendering the slot as a fragment.
-    const parent = context.parent
-    let componentType
-    if (
-      parent &&
-      parent.type === NodeTypes.ELEMENT &&
-      parent.tagType === ElementTypes.COMPONENT &&
-      ((componentType = resolveComponentType(parent, context, true)) ===
-        TRANSITION ||
-        componentType === TRANSITION_GROUP) &&
-      parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
-    ) {
-      method = SSR_RENDER_SLOT_INNER
-      if (!(context.scopeId && context.slotted !== false)) {
-        args.push('null')
+    // transition/transition-group will unwrap the slot fragment into vnode(s)
+    // at runtime, we need to avoid rendering the slot as a fragment.
+    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
+      if (
+        parent.type === NodeTypes.ELEMENT &&
+        parent.tagType === ElementTypes.COMPONENT &&
+        ((componentType = resolveComponentType(parent, context, true)) ===
+          TRANSITION ||
+          componentType === TRANSITION_GROUP) &&
+        children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
+      ) {
+        method = SSR_RENDER_SLOT_INNER
+        if (!(context.scopeId && context.slotted !== false)) {
+          args.push('null')
+        }
+        args.push('true')
       }
-      args.push('true')
     }
 
     node.ssrCodegenNode = createCallExpression(context.helper(method), args)