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) } }