text: /* @flow */
  
  import VNode from './vnode'
  import { createElement } from './create-element'
  import { resolveConstructorOptions } from '../instance/init'
  import { resolveSlots } from '../instance/render-helpers/resolve-slots'
  
  import {
    tip,
    warn,
    isObject,
    hasOwn,
    hyphenate,
    validateProp,
    formatComponentName
  } from '../util/index'
  
  import {
    callHook,
    activeInstance,
    updateChildComponent,
    activateChildComponent,
    deactivateChildComponent
  } from '../instance/lifecycle'
  
  // hooks to be invoked on component VNodes during patch
  const componentVNodeHooks = {
    init (
      vnode: VNodeWithData,
      hydrating: boolean,
      parentElm: ?Node,
      refElm: ?Node
    ): ?boolean {
      if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
        const child = vnode.componentInstance = createComponentInstanceForVnode(
          vnode,
          activeInstance,
          parentElm,
          refElm
        )
        child.$mount(hydrating ? vnode.elm : undefined, hydrating)
      } else if (vnode.data.keepAlive) {
        // kept-alive components, treat as a patch
        const mountedNode: any = vnode // work around flow
        componentVNodeHooks.prepatch(mountedNode, mountedNode)
      }
    },
  
    prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
      debugger;
      const options = vnode.componentOptions
      const child = vnode.componentInstance = oldVnode.componentInstance
      updateChildComponent(
        child,
        options.propsData, // updated props
        options.listeners, // updated listeners
        vnode, // new parent vnode
        options.children // new children
      )
    },
  
    insert (vnode: MountedComponentVNode) {
      if (!vnode.componentInstance._isMounted) {
        vnode.componentInstance._isMounted = true
        callHook(vnode.componentInstance, 'mounted')
      }
      if (vnode.data.keepAlive) {
        activateChildComponent(vnode.componentInstance, true /* direct */)
      }
    },
  
    destroy (vnode: MountedComponentVNode) {
      if (!vnode.componentInstance._isDestroyed) {
        if (!vnode.data.keepAlive) {
          vnode.componentInstance.$destroy()
        } else {
          deactivateChildComponent(vnode.componentInstance, true /* direct */)
        }
      }
    }
  }
  
  const hooksToMerge = Object.keys(componentVNodeHooks)
  
  export function createComponent (
    Ctor: Class<Component> | Function | Object | void,
    data?: VNodeData,
    context: Component,
    children: ?Array<VNode>,
    tag?: string
  ): VNode | void {
    if (!Ctor) {
      return
    }
  
    const baseCtor = context.$options._base
    if (isObject(Ctor)) {
      Ctor = baseCtor.extend(Ctor)
    }
  
    if (typeof Ctor !== 'function') {
      if (process.env.NODE_ENV !== 'production') {
        warn(`Invalid Component definition: ${String(Ctor)}`, context)
      }
      return
    }
  
    // 异步组件
    // async component
    if (!Ctor.cid) {
      if (Ctor.resolved) {
        Ctor = Ctor.resolved
      } else {
        Ctor = resolveAsyncComponent(Ctor, baseCtor, () => {
          // it's ok to queue this on every render because
          // $forceUpdate is buffered by the scheduler.
          context.$forceUpdate()
        })
        if (!Ctor) {
          // return nothing if this is indeed an async component
          // wait for the callback to trigger parent update.
          return
        }
      }
    }
  
    // resolve constructor options in case global mixins are applied after
    // component constructor creation
    resolveConstructorOptions(Ctor)
  
    data = data || {}
  
    // transform component v-model data into props & events
    if (data.model) {
      transformModel(Ctor.options, data)
    }
  
    // extract props
    const propsData = extractProps(data, Ctor, tag)
  
    // functional component
    if (Ctor.options.functional) {
      return createFunctionalComponent(Ctor, propsData, data, context, children)
    }
  
    // extract listeners, since these needs to be treated as
    // child component listeners instead of DOM listeners
    const listeners = data.on
    // replace with listeners with .native modifier
    data.on = data.nativeOn
  
    if (Ctor.options.abstract) {
      // abstract components do not keep anything
      // other than props & listeners
      data = {}
    }
  
    // merge component management hooks onto the placeholder node
    mergeHooks(data)
  
    // return a placeholder vnode
    const name = Ctor.options.name || tag
    const vnode = new VNode(
      `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
      data, undefined, undefined, undefined, context,
      { Ctor, propsData, listeners, tag, children }
    )
    return vnode
  }
  
  function createFunctionalComponent (
    Ctor: Class<Component>,
    propsData: ?Object,
    data: VNodeData,
    context: Component,
    children: ?Array<VNode>
  ): VNode | void {
    const props = {}
    const propOptions = Ctor.options.props
    if (propOptions) {
      for (const key in propOptions) {
        props[key] = validateProp(key, propOptions, propsData)
      }
    }
    // ensure the createElement function in functional components
    // gets a unique context - this is necessary for correct named slot check
    const _context = Object.create(context)
    const h = (a, b, c, d) => createElement(_context, a, b, c, d, true)
    const vnode = Ctor.options.render.call(null, h, {
      props,
      data,
      parent: context,
      children,
      slots: () => resolveSlots(children, context)
    })
    if (vnode instanceof VNode) {
      vnode.functionalContext = context
      if (data.slot) {
        (vnode.data || (vnode.data = {})).slot = data.slot
      }
    }
    return vnode
  }
  
  export function createComponentInstanceForVnode (
    vnode: any, // we know it's MountedComponentVNode but flow doesn't
    parent: any, // activeInstance in lifecycle state
    parentElm?: ?Node,
    refElm?: ?Node
  ): Component {
    const vnodeComponentOptions = vnode.componentOptions
    const options: InternalComponentOptions = {
      _isComponent: true,
      parent,
      propsData: vnodeComponentOptions.propsData,
      _componentTag: vnodeComponentOptions.tag,
      _parentVnode: vnode,
      _parentListeners: vnodeComponentOptions.listeners,
      _renderChildren: vnodeComponentOptions.children,
      _parentElm: parentElm || null,
      _refElm: refElm || null
    }
    // check inline-template render functions
    const inlineTemplate = vnode.data.inlineTemplate
    if (inlineTemplate) {
      options.render = inlineTemplate.render
      options.staticRenderFns = inlineTemplate.staticRenderFns
    }
    return new vnodeComponentOptions.Ctor(options)
  }
  
  function resolveAsyncComponent (
    factory: Function,
    baseCtor: Class<Component>,
    cb: Function
  ): Class<Component> | void {
    if (factory.requested) {
      // pool callbacks
      factory.pendingCallbacks.push(cb)
    } else {
      factory.requested = true
      const cbs = factory.pendingCallbacks = [cb]
      let sync = true
  
      const resolve = (res: Object | Class<Component>) => {
        if (isObject(res)) {
          res = baseCtor.extend(res)
        }
        // cache resolved
        factory.resolved = res
        // invoke callbacks only if this is not a synchronous resolve
        // (async resolves are shimmed as synchronous during SSR)
        if (!sync) {
          for (let i = 0, l = cbs.length; i < l; i++) {
            cbs[i](res)
          }
        }
      }
  
      const reject = reason => {
        process.env.NODE_ENV !== 'production' && warn(
          `Failed to resolve async component: ${String(factory)}` +
          (reason ? `\nReason: ${reason}` : '')
        )
      }
  
      const res = factory(resolve, reject)
  
      // handle promise
      if (res && typeof res.then === 'function' && !factory.resolved) {
        res.then(resolve, reject)
      }
  
      sync = false
      // return in case resolved synchronously
      return factory.resolved
    }
  }
  
  function extractProps (data: VNodeData, Ctor: Class<Component>, tag?: string): ?Object {
    // we are only extracting raw values here.
    // validation and default values are handled in the child
    // component itself.
    const propOptions = Ctor.options.props
    if (!propOptions) {
      return
    }
    const res = {}
    // props没有找到添加的地方
    const { attrs, props, domProps } = data
    if (attrs || props || domProps) {
      for (const key in propOptions) {
        const altKey = hyphenate(key)
        if (process.env.NODE_ENV !== 'production') {
          const keyInLowerCase = key.toLowerCase()
          if (
            key !== keyInLowerCase &&
            attrs && attrs.hasOwnProperty(keyInLowerCase)
          ) {
            tip(
              `Prop "${keyInLowerCase}" is passed to component ` +
              `${formatComponentName(tag || Ctor)}, but the declared prop name is` +
              ` "${key}". ` +
              `Note that HTML attributes are case-insensitive and camelCased ` +
              `props need to use their kebab-case equivalents when using in-DOM ` +
              `templates. You should probably use "${altKey}" instead of "${key}".`
            )
          }
        }
        checkProp(res, props, key, altKey, true) ||
        checkProp(res, attrs, key, altKey) ||
        checkProp(res, domProps, key, altKey)
      }
    }
    return res
  }
  
  function checkProp (
    res: Object,
    hash: ?Object,
    key: string,
    altKey: string,
    preserve?: boolean
  ): boolean {
    if (hash) {
      if (hasOwn(hash, key)) {
        res[key] = hash[key]
        if (!preserve) {
          delete hash[key]
        }
        return true
      } else if (hasOwn(hash, altKey)) {
        res[key] = hash[altKey]
        if (!preserve) {
          delete hash[altKey]
        }
        return true
      }
    }
    return false
  }
  
  function mergeHooks (data: VNodeData) {
    if (!data.hook) {
      data.hook = {}
    }
    for (let i = 0; i < hooksToMerge.length; i++) {
      const key = hooksToMerge[i]
      const fromParent = data.hook[key]
      const ours = componentVNodeHooks[key]
      data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
    }
  }
  
  function mergeHook (one: Function, two: Function): Function {
    return function (a, b, c, d) {
      one(a, b, c, d)
      two(a, b, c, d)
    }
  }
  
  // transform component v-model info (value and callback) into
  // prop and event handler respectively.
  function transformModel (options, data: any) {
    const prop = (options.model && options.model.prop) || 'value'
    const event = (options.model && options.model.event) || 'input'
    ;(data.props || (data.props = {}))[prop] = data.model.value
    const on = data.on || (data.on = {})
    if (on[event]) {
      on[event] = [data.model.callback].concat(on[event])
    } else {
      on[event] = data.model.callback
    }
  }
  