fix(attr-fallthrough): ensure consistent attr fallthrough for root fragments with comments

fix #2549
This commit is contained in:
Evan You
2020-11-27 18:04:26 -05:00
parent 3532b2b021
commit 3bc2914e32
5 changed files with 170 additions and 88 deletions

View File

@@ -13,6 +13,7 @@ import {
createCommentVNode,
Fragment
} from '@vue/runtime-dom'
import { PatchFlags } from '@vue/shared/src'
describe('attribute fallthrough', () => {
it('should allow attrs to fallthrough', async () => {
@@ -574,11 +575,16 @@ describe('attribute fallthrough', () => {
setup() {
return () => (
openBlock(),
createBlock(Fragment, null, [
createCommentVNode('hello'),
h('button'),
createCommentVNode('world')
])
createBlock(
Fragment,
null,
[
createCommentVNode('hello'),
h('button'),
createCommentVNode('world')
],
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT
)
)
}
}

View File

@@ -9,7 +9,6 @@ import {
createVNode,
Comment,
cloneVNode,
Fragment,
VNodeArrayChildren,
isVNode
} from './vnode'
@@ -20,8 +19,10 @@ import { isHmrUpdating } from './hmr'
import { NormalizedProps } from './componentProps'
import { isEmitListener } from './componentEmits'
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
/**
* mark the current rendering instance for asset resolution (e.g.
* resolveComponent, resolveDirective) during render
*/
export let currentRenderingInstance: ComponentInternalInstance | null = null
export function setCurrentRenderingInstance(
@@ -30,9 +31,11 @@ export function setCurrentRenderingInstance(
currentRenderingInstance = instance
}
// dev only flag to track whether $attrs was used during render.
// If $attrs was used during render then the warning for failed attrs
// fallthrough can be suppressed.
/**
* dev only flag to track whether $attrs was used during render.
* If $attrs was used during render then the warning for failed attrs
* fallthrough can be suppressed.
*/
let accessedAttrs: boolean = false
export function markAttrsAccessed() {
@@ -116,7 +119,7 @@ export function renderComponentRoot(
// to have comments along side the root element which makes it a fragment
let root = result
let setRoot: ((root: VNode) => void) | undefined = undefined
if (__DEV__) {
if (__DEV__ && result.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT) {
;[root, setRoot] = getChildRoot(result)
}
@@ -222,9 +225,6 @@ export function renderComponentRoot(
const getChildRoot = (
vnode: VNode
): [VNode, ((root: VNode) => void) | undefined] => {
if (vnode.type !== Fragment) {
return [vnode, undefined]
}
const rawChildren = vnode.children as VNodeArrayChildren
const dynamicChildren = vnode.dynamicChildren
const childRoot = filterSingleRoot(rawChildren)
@@ -246,18 +246,27 @@ const getChildRoot = (
return [normalizeVNode(childRoot), setRoot]
}
/**
* dev only
*/
export function filterSingleRoot(children: VNodeArrayChildren): VNode | null {
const filtered = children.filter(child => {
return !(
isVNode(child) &&
child.type === Comment &&
child.children !== 'v-if'
)
})
return filtered.length === 1 && isVNode(filtered[0]) ? filtered[0] : null
export function filterSingleRoot(
children: VNodeArrayChildren
): VNode | undefined {
let singleRoot
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isVNode(child)) {
// ignore user comment
if (child.type !== Comment || child.children === 'v-if') {
if (singleRoot) {
// has more than 1 non-comment child, return now
return
} else {
singleRoot = child
}
}
} else {
return
}
}
return singleRoot
}
const getFunctionalFallthrough = (attrs: Data): Data | undefined => {