Skip to content

Commit d35b877

Browse files
authored
fix(runtime-core): filter single root for nested DEV_ROOT_FRAGMENT (#8593)
close #5203 close #8581 close #10087
1 parent 8d04205 commit d35b877

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed

packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
type FunctionalComponent,
99
createBlock,
1010
createCommentVNode,
11+
createElementBlock,
12+
createElementVNode,
1113
defineComponent,
1214
h,
1315
mergeProps,
@@ -673,6 +675,58 @@ describe('attribute fallthrough', () => {
673675
expect(click).toHaveBeenCalled()
674676
})
675677

678+
it('should support fallthrough for nested dev root fragments', async () => {
679+
const toggle = ref(false)
680+
681+
const Child = {
682+
setup() {
683+
return () => (
684+
openBlock(),
685+
createElementBlock(
686+
Fragment,
687+
null,
688+
[
689+
createCommentVNode(' comment A '),
690+
toggle.value
691+
? (openBlock(), createElementBlock('span', { key: 0 }, 'Foo'))
692+
: (openBlock(),
693+
createElementBlock(
694+
Fragment,
695+
{ key: 1 },
696+
[
697+
createCommentVNode(' comment B '),
698+
createElementVNode('div', null, 'Bar'),
699+
],
700+
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
701+
)),
702+
],
703+
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
704+
)
705+
)
706+
},
707+
}
708+
709+
const Root = {
710+
setup() {
711+
return () => (openBlock(), createBlock(Child, { class: 'red' }))
712+
},
713+
}
714+
715+
const root = document.createElement('div')
716+
document.body.appendChild(root)
717+
render(h(Root), root)
718+
719+
expect(root.innerHTML).toBe(
720+
`<!-- comment A --><!-- comment B --><div class="red">Bar</div>`,
721+
)
722+
723+
toggle.value = true
724+
await nextTick()
725+
expect(root.innerHTML).toBe(
726+
`<!-- comment A --><span class=\"red\">Foo</span>`,
727+
)
728+
})
729+
676730
// #1989
677731
it('should not fallthrough v-model listeners with corresponding declared prop', () => {
678732
let textFoo = ''

packages/runtime-core/__tests__/scopeId.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import {
2+
Fragment,
3+
createBlock,
4+
createCommentVNode,
5+
createVNode,
6+
defineComponent,
27
h,
8+
nextTick,
39
nodeOps,
10+
openBlock,
411
popScopeId,
512
pushScopeId,
13+
ref,
614
render,
715
renderSlot,
816
serializeInner,
917
withScopeId,
1018
} from '@vue/runtime-test'
1119
import { withCtx } from '../src/componentRenderContext'
20+
import { PatchFlags } from '@vue/shared'
1221

1322
describe('scopeId runtime support', () => {
1423
test('should attach scopeId', () => {
@@ -184,6 +193,55 @@ describe('scopeId runtime support', () => {
184193

185194
expect(serializeInner(root)).toBe(`<div parent></div>`)
186195
})
196+
197+
test('should inherit scopeId through nested DEV_ROOT_FRAGMENT with inheritAttrs: false', async () => {
198+
const Parent = {
199+
__scopeId: 'parent',
200+
render() {
201+
return h(Child, { class: 'foo' })
202+
},
203+
}
204+
205+
const ok = ref(true)
206+
const Child = defineComponent({
207+
inheritAttrs: false,
208+
render() {
209+
return (
210+
openBlock(),
211+
createBlock(
212+
Fragment,
213+
null,
214+
[
215+
createCommentVNode('comment1'),
216+
ok.value
217+
? (openBlock(), createBlock('div', { key: 0 }, 'div1'))
218+
: (openBlock(),
219+
createBlock(
220+
Fragment,
221+
{ key: 1 },
222+
[
223+
createCommentVNode('comment2'),
224+
createVNode('div', null, 'div2'),
225+
],
226+
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
227+
)),
228+
],
229+
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
230+
)
231+
)
232+
},
233+
})
234+
235+
const root = nodeOps.createElement('div')
236+
render(h(Parent), root)
237+
expect(serializeInner(root)).toBe(`<!--comment1--><div parent>div1</div>`)
238+
239+
ok.value = false
240+
await nextTick()
241+
expect(serializeInner(root)).toBe(
242+
`<!--comment1--><!--comment2--><div parent>div2</div>`,
243+
)
244+
})
187245
})
188246

189247
describe('backwards compat with <=3.0.7', () => {

packages/runtime-core/src/componentRenderUtils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,17 @@ export function renderComponentRoot(
266266
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
267267
const rawChildren = vnode.children as VNodeArrayChildren
268268
const dynamicChildren = vnode.dynamicChildren
269-
const childRoot = filterSingleRoot(rawChildren)
269+
const childRoot = filterSingleRoot(rawChildren, false)
270270
if (!childRoot) {
271271
return [vnode, undefined]
272+
} else if (
273+
__DEV__ &&
274+
childRoot.patchFlag > 0 &&
275+
childRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
276+
) {
277+
return getChildRoot(childRoot)
272278
}
279+
273280
const index = rawChildren.indexOf(childRoot)
274281
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
275282
const setRoot: SetRootFn = (updatedRoot: VNode) => {
@@ -287,6 +294,7 @@ const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
287294

288295
export function filterSingleRoot(
289296
children: VNodeArrayChildren,
297+
recurse = true,
290298
): VNode | undefined {
291299
let singleRoot
292300
for (let i = 0; i < children.length; i++) {
@@ -299,6 +307,14 @@ export function filterSingleRoot(
299307
return
300308
} else {
301309
singleRoot = child
310+
if (
311+
__DEV__ &&
312+
recurse &&
313+
singleRoot.patchFlag > 0 &&
314+
singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
315+
) {
316+
return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
317+
}
302318
}
303319
}
304320
} else {

0 commit comments

Comments
 (0)