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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user