]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): filter single root for nested DEV_ROOT_FRAGMENT (#8593)
authoredison <daiwei521@126.com>
Fri, 12 Jan 2024 14:07:06 +0000 (22:07 +0800)
committerGitHub <noreply@github.com>
Fri, 12 Jan 2024 14:07:06 +0000 (22:07 +0800)
close #5203
close #8581
close #10087

packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
packages/runtime-core/__tests__/scopeId.spec.ts
packages/runtime-core/src/componentRenderUtils.ts

index ef5630ff65a6aaac8ff0409eaa8b550b775d4b8f..79e2867ad699d106834eaf51963a4b410766dcd4 100644 (file)
@@ -8,6 +8,8 @@ import {
   type FunctionalComponent,
   createBlock,
   createCommentVNode,
+  createElementBlock,
+  createElementVNode,
   defineComponent,
   h,
   mergeProps,
@@ -673,6 +675,58 @@ describe('attribute fallthrough', () => {
     expect(click).toHaveBeenCalled()
   })
 
+  it('should support fallthrough for nested dev root fragments', async () => {
+    const toggle = ref(false)
+
+    const Child = {
+      setup() {
+        return () => (
+          openBlock(),
+          createElementBlock(
+            Fragment,
+            null,
+            [
+              createCommentVNode(' comment A '),
+              toggle.value
+                ? (openBlock(), createElementBlock('span', { key: 0 }, 'Foo'))
+                : (openBlock(),
+                  createElementBlock(
+                    Fragment,
+                    { key: 1 },
+                    [
+                      createCommentVNode(' comment B '),
+                      createElementVNode('div', null, 'Bar'),
+                    ],
+                    PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
+                  )),
+            ],
+            PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
+          )
+        )
+      },
+    }
+
+    const Root = {
+      setup() {
+        return () => (openBlock(), createBlock(Child, { class: 'red' }))
+      },
+    }
+
+    const root = document.createElement('div')
+    document.body.appendChild(root)
+    render(h(Root), root)
+
+    expect(root.innerHTML).toBe(
+      `<!-- comment A --><!-- comment B --><div class="red">Bar</div>`,
+    )
+
+    toggle.value = true
+    await nextTick()
+    expect(root.innerHTML).toBe(
+      `<!-- comment A --><span class=\"red\">Foo</span>`,
+    )
+  })
+
   // #1989
   it('should not fallthrough v-model listeners with corresponding declared prop', () => {
     let textFoo = ''
index fb705cfad090d4eeebde80c24ce1a1f4ae70ec71..08753e023a1a079ccc825195eefbb97e9c483546 100644 (file)
@@ -1,14 +1,23 @@
 import {
+  Fragment,
+  createBlock,
+  createCommentVNode,
+  createVNode,
+  defineComponent,
   h,
+  nextTick,
   nodeOps,
+  openBlock,
   popScopeId,
   pushScopeId,
+  ref,
   render,
   renderSlot,
   serializeInner,
   withScopeId,
 } from '@vue/runtime-test'
 import { withCtx } from '../src/componentRenderContext'
+import { PatchFlags } from '@vue/shared'
 
 describe('scopeId runtime support', () => {
   test('should attach scopeId', () => {
@@ -184,6 +193,55 @@ describe('scopeId runtime support', () => {
 
     expect(serializeInner(root)).toBe(`<div parent></div>`)
   })
+
+  test('should inherit scopeId through nested DEV_ROOT_FRAGMENT with inheritAttrs: false', async () => {
+    const Parent = {
+      __scopeId: 'parent',
+      render() {
+        return h(Child, { class: 'foo' })
+      },
+    }
+
+    const ok = ref(true)
+    const Child = defineComponent({
+      inheritAttrs: false,
+      render() {
+        return (
+          openBlock(),
+          createBlock(
+            Fragment,
+            null,
+            [
+              createCommentVNode('comment1'),
+              ok.value
+                ? (openBlock(), createBlock('div', { key: 0 }, 'div1'))
+                : (openBlock(),
+                  createBlock(
+                    Fragment,
+                    { key: 1 },
+                    [
+                      createCommentVNode('comment2'),
+                      createVNode('div', null, 'div2'),
+                    ],
+                    PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
+                  )),
+            ],
+            PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
+          )
+        )
+      },
+    })
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+    expect(serializeInner(root)).toBe(`<!--comment1--><div parent>div1</div>`)
+
+    ok.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe(
+      `<!--comment1--><!--comment2--><div parent>div2</div>`,
+    )
+  })
 })
 
 describe('backwards compat with <=3.0.7', () => {
index 4cb29180e25c9fa0fb865f0b63bdb4319173d38b..4b83c69903140c16a93ff3158dc132bc30f180f3 100644 (file)
@@ -266,10 +266,17 @@ export function renderComponentRoot(
 const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
   const rawChildren = vnode.children as VNodeArrayChildren
   const dynamicChildren = vnode.dynamicChildren
-  const childRoot = filterSingleRoot(rawChildren)
+  const childRoot = filterSingleRoot(rawChildren, false)
   if (!childRoot) {
     return [vnode, undefined]
+  } else if (
+    __DEV__ &&
+    childRoot.patchFlag > 0 &&
+    childRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
+  ) {
+    return getChildRoot(childRoot)
   }
+
   const index = rawChildren.indexOf(childRoot)
   const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
   const setRoot: SetRootFn = (updatedRoot: VNode) => {
@@ -287,6 +294,7 @@ const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
 
 export function filterSingleRoot(
   children: VNodeArrayChildren,
+  recurse = true,
 ): VNode | undefined {
   let singleRoot
   for (let i = 0; i < children.length; i++) {
@@ -299,6 +307,14 @@ export function filterSingleRoot(
           return
         } else {
           singleRoot = child
+          if (
+            __DEV__ &&
+            recurse &&
+            singleRoot.patchFlag > 0 &&
+            singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
+          ) {
+            return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
+          }
         }
       }
     } else {