fix(runtime-core): filter single root for nested DEV_ROOT_FRAGMENT (#8593)

close #5203
close #8581
close #10087
This commit is contained in:
edison 2024-01-12 22:07:06 +08:00 committed by GitHub
parent 8d04205041
commit d35b87725a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 1 deletions

View 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 = ''

View 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', () => {

View 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 {