diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
index 5e3fdf588bc..2dca9c55f60 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
@@ -143,9 +143,9 @@ return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent(\\"Comp\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
- _createVNode(\\"div\\", _hoisted_1, [
+ (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [
_createVNode(_component_Comp)
- ])
+ ]))
]))
}
}"
diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
index 33cfe51dd7e..dede2bc7d98 100644
--- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts
@@ -890,4 +890,24 @@ describe('compiler: element transform', () => {
isBlock: true
})
})
+
+ // #2169
+ test('elements with components child should be forced into blocks', () => {
+ const ast = parse(`
`)
+ transform(ast, {
+ nodeTransforms: [transformElement]
+ })
+ expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"section"`,
+ isBlock: false
+ })
+ expect(
+ (ast as any).children[0].children[0].children[0].codegenNode
+ ).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"h1"`,
+ isBlock: true
+ })
+ })
})
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 8a6d39a376a..6f699597512 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -18,7 +18,8 @@ import {
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
- createVNodeCall
+ createVNodeCall,
+ PlainElementNode
} from '../ast'
import {
PatchFlags,
@@ -101,7 +102,9 @@ export const transformElement: NodeTransform = (node, context) => {
(tag === 'svg' ||
tag === 'foreignObject' ||
// #938: elements with dynamic keys should be forced into blocks
- findProp(node, 'key', true)))
+ findProp(node, 'key', true) ||
+ // #2169: elements with components child should be forced into blocks
+ hasComponentChild(node as PlainElementNode)))
// props
if (props.length > 0) {
@@ -590,3 +593,23 @@ function stringifyDynamicPropNames(props: string[]): string {
}
return propsNamesString + `]`
}
+
+function hasComponentChild(node: PlainElementNode): boolean {
+ for (let i = 0; i < node.children.length; i++) {
+ const child = node.children[i]
+ if (
+ child.type === NodeTypes.ELEMENT &&
+ child.codegenNode &&
+ child.codegenNode.type === NodeTypes.VNODE_CALL &&
+ !child.codegenNode.isBlock
+ ) {
+ switch (child.tagType) {
+ case ElementTypes.ELEMENT:
+ return hasComponentChild(child)
+ case ElementTypes.COMPONENT:
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
index 4a4b363f7b8..3e059054fd2 100644
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ b/packages/runtime-core/src/componentRenderUtils.ts
@@ -238,6 +238,10 @@ const getChildRoot = (
if (dynamicIndex > -1) {
dynamicChildren[dynamicIndex] = updatedRoot
} else if (dynamicChildren && updatedRoot.patchFlag > 0) {
+ // fix: #2169
+ // since we can guarantee that it is a single root,
+ // we can directly use it to replace the dynamicChildren to avoid redundant patches.
+ dynamicChildren.length = 0
dynamicChildren.push(updatedRoot)
}
}