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,
 | 
			
		||||
  openBlock,
 | 
			
		||||
  createBlock,
 | 
			
		||||
  FunctionalComponent
 | 
			
		||||
  FunctionalComponent,
 | 
			
		||||
  createCommentVNode
 | 
			
		||||
} from '@vue/runtime-dom'
 | 
			
		||||
import { mockWarn } from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
@ -495,4 +496,35 @@ describe('attribute fallthrough', () => {
 | 
			
		||||
    expect(onClick).toHaveBeenCalledTimes(1)
 | 
			
		||||
    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,
 | 
			
		||||
  createVNode,
 | 
			
		||||
  Comment,
 | 
			
		||||
  cloneVNode
 | 
			
		||||
  cloneVNode,
 | 
			
		||||
  Fragment,
 | 
			
		||||
  VNodeArrayChildren,
 | 
			
		||||
  isVNode
 | 
			
		||||
} from './vnode'
 | 
			
		||||
import { handleError, ErrorCodes } from './errorHandling'
 | 
			
		||||
import { PatchFlags, ShapeFlags, EMPTY_OBJ, isOn } from '@vue/shared'
 | 
			
		||||
@ -80,22 +83,30 @@ export function renderComponentRoot(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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 (
 | 
			
		||||
      Component.inheritAttrs !== false &&
 | 
			
		||||
      fallthroughAttrs &&
 | 
			
		||||
      fallthroughAttrs !== EMPTY_OBJ
 | 
			
		||||
    ) {
 | 
			
		||||
      if (
 | 
			
		||||
        result.shapeFlag & ShapeFlags.ELEMENT ||
 | 
			
		||||
        result.shapeFlag & ShapeFlags.COMPONENT
 | 
			
		||||
        root.shapeFlag & ShapeFlags.ELEMENT ||
 | 
			
		||||
        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
 | 
			
		||||
        // force update full props to account for the merged attrs.
 | 
			
		||||
        if (result.dynamicChildren) {
 | 
			
		||||
          result.patchFlag |= PatchFlags.FULL_PROPS
 | 
			
		||||
        if (root.dynamicChildren) {
 | 
			
		||||
          root.patchFlag |= PatchFlags.FULL_PROPS
 | 
			
		||||
        }
 | 
			
		||||
      } else if (__DEV__ && !accessedAttrs && result.type !== Comment) {
 | 
			
		||||
      } else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
 | 
			
		||||
        warn(
 | 
			
		||||
          `Extraneous non-props attributes (` +
 | 
			
		||||
            `${Object.keys(attrs).join(', ')}) ` +
 | 
			
		||||
@ -108,27 +119,33 @@ export function renderComponentRoot(
 | 
			
		||||
    // inherit scopeId
 | 
			
		||||
    const parentScopeId = parent && parent.type.__scopeId
 | 
			
		||||
    if (parentScopeId) {
 | 
			
		||||
      result = cloneVNode(result, { [parentScopeId]: '' })
 | 
			
		||||
      root = cloneVNode(root, { [parentScopeId]: '' })
 | 
			
		||||
    }
 | 
			
		||||
    // inherit directives
 | 
			
		||||
    if (vnode.dirs) {
 | 
			
		||||
      if (__DEV__ && !isElementRoot(result)) {
 | 
			
		||||
      if (__DEV__ && !isElementRoot(root)) {
 | 
			
		||||
        warn(
 | 
			
		||||
          `Runtime directive used on component with non-element root node. ` +
 | 
			
		||||
            `The directives will not function as intended.`
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      result.dirs = vnode.dirs
 | 
			
		||||
      root.dirs = vnode.dirs
 | 
			
		||||
    }
 | 
			
		||||
    // inherit transition data
 | 
			
		||||
    if (vnode.transition) {
 | 
			
		||||
      if (__DEV__ && !isElementRoot(result)) {
 | 
			
		||||
      if (__DEV__ && !isElementRoot(root)) {
 | 
			
		||||
        warn(
 | 
			
		||||
          `Component inside <Transition> renders non-element root node ` +
 | 
			
		||||
            `that cannot be animated.`
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      result.transition = vnode.transition
 | 
			
		||||
      root.transition = vnode.transition
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (__DEV__ && setRoot) {
 | 
			
		||||
      setRoot(root)
 | 
			
		||||
    } else {
 | 
			
		||||
      result = root
 | 
			
		||||
    }
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
 | 
			
		||||
@ -139,6 +156,25 @@ export function renderComponentRoot(
 | 
			
		||||
  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 => {
 | 
			
		||||
  let res: Data | undefined
 | 
			
		||||
  for (const key in attrs) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user