fix(runtime-core): support attr merging on child with root level comments
fix #904
This commit is contained in:
parent
5bf72517ce
commit
e42cb54394
@ -9,7 +9,8 @@ import {
|
|||||||
defineComponent,
|
defineComponent,
|
||||||
openBlock,
|
openBlock,
|
||||||
createBlock,
|
createBlock,
|
||||||
FunctionalComponent
|
FunctionalComponent,
|
||||||
|
createCommentVNode
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { mockWarn } from '@vue/shared'
|
import { mockWarn } from '@vue/shared'
|
||||||
|
|
||||||
@ -495,4 +496,35 @@ describe('attribute fallthrough', () => {
|
|||||||
expect(onClick).toHaveBeenCalledTimes(1)
|
expect(onClick).toHaveBeenCalledTimes(1)
|
||||||
expect(onClick).toHaveBeenCalledWith('custom')
|
expect(onClick).toHaveBeenCalledWith('custom')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support fallthrough for fragments with single element + comments', () => {
|
||||||
|
const click = jest.fn()
|
||||||
|
|
||||||
|
const Hello = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { class: 'foo', onClick: click })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup(props: any) {
|
||||||
|
return () => [
|
||||||
|
createCommentVNode('hello'),
|
||||||
|
h('button'),
|
||||||
|
createCommentVNode('world')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Hello), root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe(
|
||||||
|
`<!--hello--><button class="foo"></button><!--world-->`
|
||||||
|
)
|
||||||
|
const button = root.children[0] as HTMLElement
|
||||||
|
button.dispatchEvent(new CustomEvent('click'))
|
||||||
|
expect(click).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -8,7 +8,10 @@ import {
|
|||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
createVNode,
|
createVNode,
|
||||||
Comment,
|
Comment,
|
||||||
cloneVNode
|
cloneVNode,
|
||||||
|
Fragment,
|
||||||
|
VNodeArrayChildren,
|
||||||
|
isVNode
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { handleError, ErrorCodes } from './errorHandling'
|
import { handleError, ErrorCodes } from './errorHandling'
|
||||||
import { PatchFlags, ShapeFlags, EMPTY_OBJ, isOn } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, EMPTY_OBJ, isOn } from '@vue/shared'
|
||||||
@ -80,22 +83,30 @@ export function renderComponentRoot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// attr merging
|
// attr merging
|
||||||
|
// in dev mode, comments are preserved, and it's possible for a template
|
||||||
|
// 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__) {
|
||||||
|
;[root, setRoot] = getChildRoot(result)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Component.inheritAttrs !== false &&
|
Component.inheritAttrs !== false &&
|
||||||
fallthroughAttrs &&
|
fallthroughAttrs &&
|
||||||
fallthroughAttrs !== EMPTY_OBJ
|
fallthroughAttrs !== EMPTY_OBJ
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
result.shapeFlag & ShapeFlags.ELEMENT ||
|
root.shapeFlag & ShapeFlags.ELEMENT ||
|
||||||
result.shapeFlag & ShapeFlags.COMPONENT
|
root.shapeFlag & ShapeFlags.COMPONENT
|
||||||
) {
|
) {
|
||||||
result = cloneVNode(result, fallthroughAttrs)
|
root = cloneVNode(root, fallthroughAttrs)
|
||||||
// If the child root node is a compiler optimized vnode, make sure it
|
// If the child root node is a compiler optimized vnode, make sure it
|
||||||
// force update full props to account for the merged attrs.
|
// force update full props to account for the merged attrs.
|
||||||
if (result.dynamicChildren) {
|
if (root.dynamicChildren) {
|
||||||
result.patchFlag |= PatchFlags.FULL_PROPS
|
root.patchFlag |= PatchFlags.FULL_PROPS
|
||||||
}
|
}
|
||||||
} else if (__DEV__ && !accessedAttrs && result.type !== Comment) {
|
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
||||||
warn(
|
warn(
|
||||||
`Extraneous non-props attributes (` +
|
`Extraneous non-props attributes (` +
|
||||||
`${Object.keys(attrs).join(', ')}) ` +
|
`${Object.keys(attrs).join(', ')}) ` +
|
||||||
@ -108,27 +119,33 @@ export function renderComponentRoot(
|
|||||||
// inherit scopeId
|
// inherit scopeId
|
||||||
const parentScopeId = parent && parent.type.__scopeId
|
const parentScopeId = parent && parent.type.__scopeId
|
||||||
if (parentScopeId) {
|
if (parentScopeId) {
|
||||||
result = cloneVNode(result, { [parentScopeId]: '' })
|
root = cloneVNode(root, { [parentScopeId]: '' })
|
||||||
}
|
}
|
||||||
// inherit directives
|
// inherit directives
|
||||||
if (vnode.dirs) {
|
if (vnode.dirs) {
|
||||||
if (__DEV__ && !isElementRoot(result)) {
|
if (__DEV__ && !isElementRoot(root)) {
|
||||||
warn(
|
warn(
|
||||||
`Runtime directive used on component with non-element root node. ` +
|
`Runtime directive used on component with non-element root node. ` +
|
||||||
`The directives will not function as intended.`
|
`The directives will not function as intended.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result.dirs = vnode.dirs
|
root.dirs = vnode.dirs
|
||||||
}
|
}
|
||||||
// inherit transition data
|
// inherit transition data
|
||||||
if (vnode.transition) {
|
if (vnode.transition) {
|
||||||
if (__DEV__ && !isElementRoot(result)) {
|
if (__DEV__ && !isElementRoot(root)) {
|
||||||
warn(
|
warn(
|
||||||
`Component inside <Transition> renders non-element root node ` +
|
`Component inside <Transition> renders non-element root node ` +
|
||||||
`that cannot be animated.`
|
`that cannot be animated.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result.transition = vnode.transition
|
root.transition = vnode.transition
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__ && setRoot) {
|
||||||
|
setRoot(root)
|
||||||
|
} else {
|
||||||
|
result = root
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
|
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
|
||||||
@ -139,6 +156,25 @@ export function renderComponentRoot(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getChildRoot = (
|
||||||
|
vnode: VNode
|
||||||
|
): [VNode, ((root: VNode) => void) | undefined] => {
|
||||||
|
if (vnode.type !== Fragment) {
|
||||||
|
return [vnode, undefined]
|
||||||
|
}
|
||||||
|
const rawChildren = vnode.children as VNodeArrayChildren
|
||||||
|
const children = rawChildren.filter(child => {
|
||||||
|
return !(isVNode(child) && child.type === Comment)
|
||||||
|
})
|
||||||
|
if (children.length !== 1) {
|
||||||
|
return [vnode, undefined]
|
||||||
|
}
|
||||||
|
const childRoot = children[0]
|
||||||
|
const index = rawChildren.indexOf(childRoot)
|
||||||
|
const setRoot = (updatedRoot: VNode) => (rawChildren[index] = updatedRoot)
|
||||||
|
return [normalizeVNode(childRoot), setRoot]
|
||||||
|
}
|
||||||
|
|
||||||
const getFallthroughAttrs = (attrs: Data): Data | undefined => {
|
const getFallthroughAttrs = (attrs: Data): Data | undefined => {
|
||||||
let res: Data | undefined
|
let res: Data | undefined
|
||||||
for (const key in attrs) {
|
for (const key in attrs) {
|
||||||
|
Loading…
Reference in New Issue
Block a user