diff --git a/packages/runtime-core/__tests__/rendererFragment.spec.ts b/packages/runtime-core/__tests__/rendererFragment.spec.ts
index 9e1f87ae..93140f13 100644
--- a/packages/runtime-core/__tests__/rendererFragment.spec.ts
+++ b/packages/runtime-core/__tests__/rendererFragment.spec.ts
@@ -10,9 +10,13 @@ import {
dumpOps,
NodeOpTypes,
serializeInner,
- createTextVNode
+ createTextVNode,
+ createBlock,
+ openBlock,
+ createCommentVNode
} from '@vue/runtime-test'
import { PatchFlags } from '@vue/shared'
+import { renderList } from '../src/helpers/renderList'
describe('renderer: fragment', () => {
it('should allow returning multiple component root nodes', () => {
@@ -269,4 +273,46 @@ describe('renderer: fragment', () => {
render(null, root)
expect(serializeInner(root)).toBe(``)
})
+
+ // #2080
+ test('`template` keyed fragment w/ comment + hoisted node', () => {
+ const root = nodeOps.createElement('div')
+ const hoisted = h('span')
+
+ const renderFn = (items: string[]) => {
+ return (
+ openBlock(true),
+ createBlock(
+ Fragment,
+ null,
+ renderList(items, item => {
+ return (
+ openBlock(),
+ createBlock(
+ Fragment,
+ { key: item },
+ [
+ createCommentVNode('comment'),
+ hoisted,
+ createVNode('div', null, item, PatchFlags.TEXT)
+ ],
+ PatchFlags.STABLE_FRAGMENT
+ )
+ )
+ }),
+ PatchFlags.KEYED_FRAGMENT
+ )
+ )
+ }
+
+ render(renderFn(['one', 'two']), root)
+ expect(serializeInner(root)).toBe(
+ `
one
two
`
+ )
+
+ render(renderFn(['two', 'one']), root)
+ expect(serializeInner(root)).toBe(
+ `two
one
`
+ )
+ })
})
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 670cd41f..5246fcb1 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -1153,8 +1153,10 @@ function baseCreateRenderer(
parentSuspense,
isSVG
)
- if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
- traverseStaticChildren(n1, n2)
+ // #2080 if the stable fragment has a key, it's a that may
+ // get moved around. Make sure all root level vnodes inherit el.
+ if (n2.key != null) {
+ traverseStaticChildren(n1, n2, true /* shallow */)
}
} else {
// keyed / unkeyed, or manual fragments.
@@ -2166,9 +2168,12 @@ function baseCreateRenderer(
* inside a block also inherit the DOM element from the previous tree so that
* HMR updates (which are full updates) can retrieve the element for patching.
*
- * Dev only.
+ * #2080
+ * Inside keyed `template` fragment static children, if a fragment is moved,
+ * the children will always moved so that need inherit el form previous nodes
+ * to ensure correct moved position.
*/
- const traverseStaticChildren = (n1: VNode, n2: VNode) => {
+ const traverseStaticChildren = (n1: VNode, n2: VNode, shallow = false) => {
const ch1 = n1.children
const ch2 = n2.children
if (isArray(ch1) && isArray(ch2)) {
@@ -2181,7 +2186,10 @@ function baseCreateRenderer(
if (c2.patchFlag <= 0 || c2.patchFlag === PatchFlags.HYDRATE_EVENTS) {
c2.el = c1.el
}
- traverseStaticChildren(c1, c2)
+ if (!shallow) traverseStaticChildren(c1, c2)
+ }
+ if (__DEV__ && c2.type === Comment) {
+ c2.el = c1.el
}
}
}